mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
446 lines
16 KiB
C++
446 lines
16 KiB
C++
//===--- Diagnostics.cpp - Redundancy and conflict diagnostics ------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Diagnostics.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/DiagnosticsSema.h"
|
|
#include "swift/AST/Requirement.h"
|
|
#include "swift/AST/Type.h"
|
|
#include "RequirementMachine.h"
|
|
#include "RewriteSystem.h"
|
|
|
|
using namespace swift;
|
|
using namespace rewriting;
|
|
|
|
static bool shouldSuggestConcreteTypeFixit(
|
|
Type type, AllowConcreteTypePolicy concreteTypePolicy) {
|
|
switch (concreteTypePolicy) {
|
|
case AllowConcreteTypePolicy::All:
|
|
return true;
|
|
|
|
case AllowConcreteTypePolicy::AssocTypes:
|
|
return type->is<DependentMemberType>();
|
|
|
|
case AllowConcreteTypePolicy::NestedAssocTypes:
|
|
if (auto *memberType = type->getAs<DependentMemberType>())
|
|
return memberType->getBase()->is<DependentMemberType>();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Emit diagnostics for the given \c RequirementErrors.
|
|
///
|
|
/// \param ctx The AST context in which to emit diagnostics.
|
|
/// \param errors The set of requirement diagnostics to be emitted.
|
|
/// \param concreteTypePolicy Whether fix-its should be offered to turn
|
|
/// invalid type requirements, e.g. T: Int, into same-type requirements.
|
|
///
|
|
/// \returns true if any errors were emitted, and false otherwise (including
|
|
/// when only warnings were emitted).
|
|
bool swift::rewriting::diagnoseRequirementErrors(
|
|
ASTContext &ctx, ArrayRef<RequirementError> errors,
|
|
AllowConcreteTypePolicy concreteTypePolicy) {
|
|
bool diagnosedError = false;
|
|
|
|
for (auto error : errors) {
|
|
SourceLoc loc = error.loc;
|
|
if (!loc.isValid())
|
|
continue;
|
|
|
|
switch (error.kind) {
|
|
case RequirementError::Kind::InvalidTypeRequirement: {
|
|
if (error.requirement.hasError())
|
|
break;
|
|
|
|
Type subjectType = error.requirement.getFirstType();
|
|
Type constraint = error.requirement.getSecondType();
|
|
|
|
ctx.Diags.diagnose(loc, diag::requires_conformance_nonprotocol,
|
|
subjectType, constraint);
|
|
diagnosedError = true;
|
|
|
|
auto getNameWithoutSelf = [&](std::string subjectTypeName) {
|
|
std::string selfSubstring = "Self.";
|
|
|
|
if (subjectTypeName.rfind(selfSubstring, 0) == 0) {
|
|
return subjectTypeName.erase(0, selfSubstring.length());
|
|
}
|
|
|
|
return subjectTypeName;
|
|
};
|
|
|
|
if (shouldSuggestConcreteTypeFixit(subjectType, concreteTypePolicy)) {
|
|
auto options = PrintOptions::forDiagnosticArguments();
|
|
auto subjectTypeName = subjectType.getString(options);
|
|
auto subjectTypeNameWithoutSelf = getNameWithoutSelf(subjectTypeName);
|
|
ctx.Diags.diagnose(loc, diag::requires_conformance_nonprotocol_fixit,
|
|
subjectTypeNameWithoutSelf,
|
|
constraint.getString(options))
|
|
.fixItReplace(loc, " == ");
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case RequirementError::Kind::InvalidRequirementSubject: {
|
|
if (error.requirement.hasError())
|
|
break;
|
|
|
|
auto subjectType = error.requirement.getFirstType();
|
|
|
|
ctx.Diags.diagnose(loc, diag::requires_not_suitable_archetype,
|
|
subjectType);
|
|
diagnosedError = true;
|
|
break;
|
|
}
|
|
|
|
case RequirementError::Kind::ConflictingRequirement: {
|
|
auto requirement = error.requirement;
|
|
auto conflict = error.conflictingRequirement;
|
|
|
|
if (requirement.hasError())
|
|
break;
|
|
|
|
if (!conflict) {
|
|
ctx.Diags.diagnose(loc, diag::requires_same_concrete_type,
|
|
requirement.getFirstType(),
|
|
requirement.getSecondType());
|
|
} else {
|
|
if (conflict->hasError())
|
|
break;
|
|
|
|
auto options = PrintOptions::forDiagnosticArguments();
|
|
std::string requirements;
|
|
llvm::raw_string_ostream OS(requirements);
|
|
OS << "'";
|
|
requirement.print(OS, options);
|
|
OS << "' and '";
|
|
conflict->print(OS, options);
|
|
OS << "'";
|
|
|
|
ctx.Diags.diagnose(loc, diag::requirement_conflict,
|
|
requirement.getFirstType(), requirements);
|
|
}
|
|
|
|
diagnosedError = true;
|
|
break;
|
|
}
|
|
|
|
case RequirementError::Kind::RedundantRequirement: {
|
|
// We only emit redundant requirement warnings if the user passed
|
|
// the -warn-redundant-requirements frontend flag.
|
|
if (!ctx.LangOpts.WarnRedundantRequirements)
|
|
break;
|
|
|
|
auto requirement = error.requirement;
|
|
if (requirement.hasError())
|
|
break;
|
|
|
|
switch (requirement.getKind()) {
|
|
case RequirementKind::SameType:
|
|
ctx.Diags.diagnose(loc, diag::redundant_same_type_to_concrete,
|
|
requirement.getFirstType(),
|
|
requirement.getSecondType());
|
|
break;
|
|
case RequirementKind::Conformance:
|
|
ctx.Diags.diagnose(loc, diag::redundant_conformance_constraint,
|
|
requirement.getFirstType(),
|
|
requirement.getProtocolDecl());
|
|
break;
|
|
case RequirementKind::Superclass:
|
|
ctx.Diags.diagnose(loc, diag::redundant_superclass_constraint,
|
|
requirement.getFirstType(),
|
|
requirement.getSecondType());
|
|
break;
|
|
case RequirementKind::Layout:
|
|
ctx.Diags.diagnose(loc, diag::redundant_layout_constraint,
|
|
requirement.getFirstType(),
|
|
requirement.getLayoutConstraint());
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return diagnosedError;
|
|
}
|
|
|
|
/// Determine whether this is a redundantly inheritable Objective-C protocol.
|
|
///
|
|
/// A redundantly-inheritable Objective-C protocol is one where we will
|
|
/// silently accept a directly-stated redundant conformance to this protocol,
|
|
/// and emit this protocol in the list of "inherited" protocols. There are
|
|
/// two cases where we allow this:
|
|
///
|
|
// 1) For a protocol defined in Objective-C, so that we will match Clang's
|
|
/// behavior, and
|
|
/// 2) For an @objc protocol defined in Swift that directly inherits from
|
|
/// JavaScriptCore's JSExport, which depends on this behavior.
|
|
static bool isRedundantlyInheritableObjCProtocol(const ProtocolDecl *inheritingProto,
|
|
const ProtocolDecl *proto) {
|
|
if (!proto->isObjC()) return false;
|
|
|
|
// Check the two conditions in which we will suppress the diagnostic and
|
|
// emit the redundant inheritance.
|
|
if (!inheritingProto->hasClangNode() && !proto->getName().is("JSExport"))
|
|
return false;
|
|
|
|
// If the inheriting protocol already has @_restatedObjCConformance with
|
|
// this protocol, we're done.
|
|
for (auto *attr : inheritingProto->getAttrs()
|
|
.getAttributes<RestatedObjCConformanceAttr>()) {
|
|
if (attr->Proto == proto) return true;
|
|
}
|
|
|
|
// Otherwise, add @_restatedObjCConformance.
|
|
auto &ctx = proto->getASTContext();
|
|
const_cast<ProtocolDecl *>(inheritingProto)
|
|
->getAttrs().add(new (ctx) RestatedObjCConformanceAttr(
|
|
const_cast<ProtocolDecl *>(proto)));
|
|
return true;
|
|
}
|
|
|
|
/// Computes the set of explicit redundant requirements to
|
|
/// emit warnings for in the source code.
|
|
void RewriteSystem::computeRedundantRequirementDiagnostics(
|
|
SmallVectorImpl<RequirementError> &errors) {
|
|
// Collect all rule IDs for each unique requirement ID.
|
|
llvm::SmallDenseMap<unsigned, llvm::SmallVector<unsigned, 2>>
|
|
rulesPerRequirement;
|
|
|
|
// Collect non-explicit requirements that are not redundant.
|
|
llvm::SmallDenseSet<unsigned, 2> nonExplicitNonRedundantRules;
|
|
|
|
for (unsigned ruleID = FirstLocalRule, e = Rules.size();
|
|
ruleID < e; ++ruleID) {
|
|
auto &rule = getRules()[ruleID];
|
|
|
|
if (rule.isPermanent())
|
|
continue;
|
|
|
|
if (!isInMinimizationDomain(rule.getLHS().getRootProtocol()))
|
|
continue;
|
|
|
|
// Concrete conformance rules do not map to requirements in the minimized
|
|
// signature; we don't consider them to be 'non-explicit non-redundant',
|
|
// so that a conformance rule (T.[P] => T) expressed in terms of a concrete
|
|
// conformance (T.[concrete: C : P] => T) is still diagnosed as redundant.
|
|
if (auto optSymbol = rule.isPropertyRule()) {
|
|
if (optSymbol->getKind() == Symbol::Kind::ConcreteConformance)
|
|
continue;
|
|
}
|
|
|
|
auto requirementID = rule.getRequirementID();
|
|
|
|
if (!requirementID.hasValue()) {
|
|
if (!rule.isRedundant())
|
|
nonExplicitNonRedundantRules.insert(ruleID);
|
|
|
|
continue;
|
|
}
|
|
|
|
rulesPerRequirement[*requirementID].push_back(ruleID);
|
|
}
|
|
|
|
// Compute the set of redundant rules which transitively reference a
|
|
// non-explicit non-redundant rule. This updates nonExplicitNonRedundantRules.
|
|
//
|
|
// Since earlier redundant paths might reference rules which appear later in
|
|
// the list but not vice versa, walk the redundant paths in reverse order.
|
|
for (const auto &pair : llvm::reverse(RedundantRules)) {
|
|
// Pre-condition: the replacement path only references redundant rules
|
|
// which we have already seen. If any of those rules transitively reference
|
|
// a non-explicit, non-redundant rule, they have been inserted into the
|
|
// nonExplicitNonRedundantRules set on previous iterations.
|
|
unsigned ruleID = pair.first;
|
|
const auto &rewritePath = pair.second;
|
|
|
|
// Check if this rewrite path references a rule that is already known to
|
|
// either be non-explicit and non-redundant, or reference such a rule via
|
|
// it's redundancy path.
|
|
for (auto step : rewritePath) {
|
|
switch (step.Kind) {
|
|
case RewriteStep::Rule: {
|
|
if (nonExplicitNonRedundantRules.count(step.getRuleID())) {
|
|
nonExplicitNonRedundantRules.insert(ruleID);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case RewriteStep::LeftConcreteProjection:
|
|
case RewriteStep::Decompose:
|
|
case RewriteStep::PrefixSubstitutions:
|
|
case RewriteStep::Shift:
|
|
case RewriteStep::Relation:
|
|
case RewriteStep::DecomposeConcrete:
|
|
case RewriteStep::RightConcreteProjection:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Post-condition: If the current replacement path transitively references
|
|
// any non-explicit, non-redundant rules, then nonExplicitNonRedundantRules
|
|
// contains the current rule.
|
|
}
|
|
|
|
// We diagnose a redundancy if the rule is redundant, and if its replacement
|
|
// path does not transitively involve any non-explicit, non-redundant rules.
|
|
auto isRedundantRule = [&](unsigned ruleID) {
|
|
const auto &rule = getRules()[ruleID];
|
|
|
|
if (!rule.isRedundant())
|
|
return false;
|
|
|
|
if (nonExplicitNonRedundantRules.count(ruleID) > 0)
|
|
return false;
|
|
|
|
if (rule.isProtocolRefinementRule(Context) &&
|
|
isRedundantlyInheritableObjCProtocol(rule.getLHS()[0].getProtocol(),
|
|
rule.getLHS()[1].getProtocol()))
|
|
return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
// Finally walk through the written requirements, diagnosing any that are
|
|
// redundant.
|
|
for (auto requirementID : indices(WrittenRequirements)) {
|
|
auto requirement = WrittenRequirements[requirementID];
|
|
|
|
// Inferred requirements can be re-stated without warning.
|
|
if (requirement.inferred)
|
|
continue;
|
|
|
|
auto pairIt = rulesPerRequirement.find(requirementID);
|
|
|
|
// If there are no rules for this structural requirement, then the
|
|
// requirement is unnecessary in the source code.
|
|
//
|
|
// This means the requirement was determined to be vacuous by
|
|
// requirement lowering and produced no rules, or the rewrite rules were
|
|
// trivially simplified by RewriteSystem::addRule().
|
|
if (pairIt == rulesPerRequirement.end()) {
|
|
errors.push_back(
|
|
RequirementError::forRedundantRequirement(requirement.req,
|
|
requirement.loc));
|
|
continue;
|
|
}
|
|
|
|
// If all rules derived from this structural requirement are redundant,
|
|
// then the requirement is unnecessary in the source code.
|
|
//
|
|
// This means the rules derived from this requirement were all
|
|
// determined to be redundant by homotopy reduction.
|
|
const auto &ruleIDs = pairIt->second;
|
|
if (llvm::all_of(ruleIDs, isRedundantRule)) {
|
|
auto requirement = WrittenRequirements[requirementID];
|
|
errors.push_back(
|
|
RequirementError::forRedundantRequirement(requirement.req,
|
|
requirement.loc));
|
|
}
|
|
}
|
|
}
|
|
|
|
static Requirement
|
|
getRequirementForDiagnostics(Type subject, Symbol property,
|
|
const PropertyMap &map,
|
|
TypeArrayView<GenericTypeParamType> genericParams,
|
|
const MutableTerm &prefix) {
|
|
switch (property.getKind()) {
|
|
case Symbol::Kind::ConcreteType: {
|
|
auto concreteType = map.getTypeFromSubstitutionSchema(
|
|
property.getConcreteType(), property.getSubstitutions(),
|
|
genericParams, prefix);
|
|
return Requirement(RequirementKind::SameType, subject, concreteType);
|
|
}
|
|
|
|
case Symbol::Kind::Superclass: {
|
|
auto concreteType = map.getTypeFromSubstitutionSchema(
|
|
property.getConcreteType(), property.getSubstitutions(),
|
|
genericParams, prefix);
|
|
return Requirement(RequirementKind::Superclass, subject, concreteType);
|
|
}
|
|
|
|
case Symbol::Kind::Protocol:
|
|
return Requirement(RequirementKind::Conformance, subject,
|
|
property.getProtocol()->getDeclaredInterfaceType());
|
|
|
|
case Symbol::Kind::Layout:
|
|
return Requirement(RequirementKind::Layout, subject,
|
|
property.getLayoutConstraint());
|
|
|
|
default:
|
|
llvm::errs() << "Bad property symbol: " << property << "\n";
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void RewriteSystem::computeConflictDiagnostics(
|
|
SmallVectorImpl<RequirementError> &errors, SourceLoc signatureLoc,
|
|
const PropertyMap &propertyMap,
|
|
TypeArrayView<GenericTypeParamType> genericParams) {
|
|
for (auto pair : ConflictingRules) {
|
|
const auto &firstRule = getRule(pair.first);
|
|
const auto &secondRule = getRule(pair.second);
|
|
|
|
assert(firstRule.isPropertyRule() && secondRule.isPropertyRule());
|
|
|
|
if (firstRule.isSubstitutionSimplified() ||
|
|
secondRule.isSubstitutionSimplified())
|
|
continue;
|
|
|
|
bool chooseFirstRule = firstRule.getRHS().size() > secondRule.getRHS().size();
|
|
auto subjectRule = chooseFirstRule ? firstRule : secondRule;
|
|
auto subjectTerm = subjectRule.getRHS();
|
|
|
|
auto suffixRule = chooseFirstRule ? secondRule : firstRule;
|
|
auto suffixTerm = suffixRule.getRHS();
|
|
|
|
// If the root protocol of the subject term isn't in this minimization
|
|
// domain, the conflict was already diagnosed.
|
|
if (!isInMinimizationDomain(subjectTerm[0].getRootProtocol()))
|
|
continue;
|
|
|
|
Type subject = propertyMap.getTypeForTerm(subjectTerm, genericParams);
|
|
MutableTerm prefix(subjectTerm.begin(), subjectTerm.end() - suffixTerm.size());
|
|
errors.push_back(RequirementError::forConflictingRequirement(
|
|
getRequirementForDiagnostics(subject, *subjectRule.isPropertyRule(),
|
|
propertyMap, genericParams, MutableTerm()),
|
|
getRequirementForDiagnostics(subject, *suffixRule.isPropertyRule(),
|
|
propertyMap, genericParams, prefix),
|
|
signatureLoc));
|
|
}
|
|
}
|
|
|
|
void RequirementMachine::computeRequirementDiagnostics(
|
|
SmallVectorImpl<RequirementError> &errors, SourceLoc signatureLoc) {
|
|
System.computeRedundantRequirementDiagnostics(errors);
|
|
System.computeConflictDiagnostics(errors, signatureLoc, Map,
|
|
getGenericParams());
|
|
}
|
|
|
|
std::string RequirementMachine::getRuleAsStringForDiagnostics(
|
|
unsigned ruleID) const {
|
|
const auto &rule = System.getRule(ruleID);
|
|
|
|
std::string result;
|
|
llvm::raw_string_ostream out(result);
|
|
out << rule;
|
|
return out.str();
|
|
}
|