Skip to content

[ConstraintSystem] Add a fix to ignore contextual type mismatch #26459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
aa212d5
[ConstraintSystem] Add a fix to ignore contextual type mismatch
xedin Aug 1, 2019
a4d7cbd
[Diagnostics] Extend contextual failure diagnostic to support all bas…
xedin Aug 1, 2019
c54913f
[TypeChecker] NFC: Fix some diagnostics improved by porting contextua…
xedin Aug 1, 2019
fba0cfa
[Diagnostics] Port tailored diagnostic for optional -> bool contextua…
xedin Aug 2, 2019
e7bff97
[Diagnostics] NFC: Temporarily move couple of auxiliary functions to …
xedin Aug 5, 2019
27b9d37
[Diagnostics] Port raw representable fix-it to contextual failure
xedin Aug 5, 2019
69589f0
[Diagnostics] Port integer cast fix-it to contextual failure
xedin Aug 5, 2019
90e38af
[Diagnostics] Port type coercion fix-it to contextual failure
xedin Aug 5, 2019
6389742
[ConstraintSystem] Delay ignoring contextual failure until restrictio…
xedin Aug 6, 2019
9526d6f
[IDE] NFC: Fix test-case which now diagnoses correctly
xedin Aug 6, 2019
0e9f7c0
[Diagnostics] Diagnose thowing -> non-throwing contextual mismatch
xedin Aug 7, 2019
fd85bfe
[Diagnostics] Port tailored diagnostic for dictionary conversions
xedin Aug 8, 2019
290ec3c
[Diagnostics] Substring -> String fix-it should be anchored at semant…
xedin Aug 8, 2019
15ae692
[ConstraintSystem] Repair and diagnose failures relared to `throws` m…
xedin Aug 12, 2019
9bbdf79
[CSDiag] NFC: Remove obsolete throws/no-escape contextual mismatch ha…
xedin Aug 12, 2019
1ecd209
[CSDiag] Diagnose `call expected no arguments` without additional typ…
xedin Aug 13, 2019
253abad
[Diagnostics] Port remaining contextual failures (expect associated w…
xedin Aug 13, 2019
129092e
[Diagnostics] NFC: Adjust couple of regressed diagnostics related to …
xedin Aug 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
611 changes: 49 additions & 562 deletions lib/Sema/CSDiag.cpp

Large diffs are not rendered by default.

516 changes: 451 additions & 65 deletions lib/Sema/CSDiagnostics.cpp

Large diffs are not rendered by default.

112 changes: 101 additions & 11 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -661,31 +661,89 @@ class AssignmentFailure final : public FailureDiagnostic {
/// Intended to diagnose any possible contextual failure
/// e.g. argument/parameter, closure result, conversions etc.
class ContextualFailure : public FailureDiagnostic {
ContextualTypePurpose CTP;
Type FromType, ToType;

public:
ContextualFailure(Expr *root, ConstraintSystem &cs, Type lhs, Type rhs,
ConstraintLocator *locator)
: FailureDiagnostic(root, cs, locator), FromType(resolve(lhs)),
ToType(resolve(rhs)) {}
: ContextualFailure(root, cs, cs.getContextualTypePurpose(), lhs, rhs,
locator) {}

Type getFromType() const { return resolveType(FromType); }
ContextualFailure(Expr *root, ConstraintSystem &cs,
ContextualTypePurpose purpose, Type lhs, Type rhs,
ConstraintLocator *locator)
: FailureDiagnostic(root, cs, locator), CTP(purpose),
FromType(resolve(lhs)), ToType(resolve(rhs)) {}

Type getFromType() const { return FromType; }

Type getToType() const { return resolveType(ToType); }
Type getToType() const { return ToType; }

bool diagnoseAsError() override;

// If we're trying to convert something of type "() -> T" to T,
// then we probably meant to call the value.
bool diagnoseMissingFunctionCall() const;

/// Produce a specialized diagnostic if this is an invalid conversion to Bool.
bool diagnoseConversionToBool() const;

/// Produce a specialized diagnostic if this is an attempt to initialize
/// or convert an array literal to a dictionary e.g. `let _: [String: Int] = ["A", 0]`
bool diagnoseConversionToDictionary() const;

/// Attempt to attach any relevant fix-its to already produced diagnostic.
void tryFixIts(InFlightDiagnostic &diagnostic) const;

/// Attempts to add fix-its for these two mistakes:
///
/// - Passing an integer where a type conforming to RawRepresentable is
/// expected, by wrapping the expression in a call to the contextual
/// type's initializer
///
/// - Passing a type conforming to RawRepresentable where an integer is
/// expected, by wrapping the expression in a call to the rawValue
/// accessor
///
/// - Return true on the fixit is added, false otherwise.
///
/// This helps migration with SDK changes.
bool
tryRawRepresentableFixIts(InFlightDiagnostic &diagnostic,
KnownProtocolKind rawRepresentablePrococol) const;

/// Attempts to add fix-its for these two mistakes:
///
/// - Passing an integer with the right type but which is getting wrapped with
/// a different integer type unnecessarily. The fixit removes the cast.
///
/// - Passing an integer but expecting different integer type. The fixit adds
/// a wrapping cast.
///
/// - Return true on the fixit is added, false otherwise.
///
/// This helps migration with SDK changes.
bool tryIntegerCastFixIts(InFlightDiagnostic &diagnostic) const;

protected:
/// Try to add a fix-it when converting between a collection and its slice
/// type, such as String <-> Substring or (eventually) Array <-> ArraySlice
static bool trySequenceSubsequenceFixIts(InFlightDiagnostic &diag,
ConstraintSystem &CS, Type fromType,
Type toType, Expr *expr);
bool trySequenceSubsequenceFixIts(InFlightDiagnostic &diagnostic) const;

/// Try to add a fix-it that suggests to explicitly use `as` or `as!`
/// to coerce one type to another if type-checker can prove that such
/// conversion is possible.
bool tryTypeCoercionFixIt(InFlightDiagnostic &diagnostic) const;

/// Check whether this contextual failure represents an invalid
/// conversion from array literal to dictionary.
static bool isInvalidDictionaryConversion(ConstraintSystem &cs, Expr *anchor,
Type contextualType);

private:
ContextualTypePurpose getContextualTypePurpose() const { return CTP; }

Type resolve(Type rawType) {
auto type = resolveType(rawType)->getWithoutSpecifierType();
if (auto *BGT = type->getAs<BoundGenericType>()) {
Expand All @@ -698,6 +756,42 @@ class ContextualFailure : public FailureDiagnostic {
/// Try to add a fix-it to convert a stored property into a computed
/// property
void tryComputedPropertyFixIts(Expr *expr) const;

bool isIntegerType(Type type) const {
return conformsToKnownProtocol(
getConstraintSystem(), type,
KnownProtocolKind::ExpressibleByIntegerLiteral);
}

/// Return true if the conversion from fromType to toType is
/// an invalid string index operation.
bool isIntegerToStringIndexConversion() const;

protected:
static Optional<Diag<Type, Type>>
getDiagnosticFor(ContextualTypePurpose context, bool forProtocol);
};

/// Diagnose failures related to conversion between throwing function type
/// and non-throwing one e.g.
///
/// ```swift
/// func foo<T>(_ t: T) throws -> Void {}
/// let _: (Int) -> Void = foo // `foo` can't be implictly converted to
/// // non-throwing type `(Int) -> Void`
/// ```
class ThrowingFunctionConversionFailure final : public ContextualFailure {
public:
ThrowingFunctionConversionFailure(Expr *root, ConstraintSystem &cs,
Type fromType, Type toType,
ConstraintLocator *locator)
: ContextualFailure(root, cs, fromType, toType, locator) {
auto fnType1 = fromType->castTo<FunctionType>();
auto fnType2 = toType->castTo<FunctionType>();
assert(fnType1->throws() != fnType2->throws());
}

bool diagnoseAsError() override;
};

/// Diagnose failures related attempt to implicitly convert types which
Expand Down Expand Up @@ -1422,10 +1516,6 @@ class MissingContextualConformanceFailure final : public ContextualFailure {
}

bool diagnoseAsError() override;

private:
static Optional<Diag<Type, Type>>
getDiagnosticFor(ContextualTypePurpose purpose);
};

/// Diagnose generic argument omission e.g.
Expand Down
29 changes: 29 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -743,3 +743,32 @@ bool AllowTupleSplatForSingleParameter::attempt(

return cs.recordFix(fix);
}

bool DropThrowsAttribute::diagnose(Expr *root, bool asNote) const {
auto &cs = getConstraintSystem();
ThrowingFunctionConversionFailure failure(root, cs, getFromType(),
getToType(), getLocator());
return failure.diagnose(asNote);
}

DropThrowsAttribute *DropThrowsAttribute::create(ConstraintSystem &cs,
FunctionType *fromType,
FunctionType *toType,
ConstraintLocator *locator) {
return new (cs.getAllocator())
DropThrowsAttribute(cs, fromType, toType, locator);
}

bool IgnoreContextualType::diagnose(Expr *root, bool asNote) const {
auto &cs = getConstraintSystem();
ContextualFailure failure(root, cs, getFromType(), getToType(), getLocator());
return failure.diagnose(asNote);
}

IgnoreContextualType *IgnoreContextualType::create(ConstraintSystem &cs,
Type resultTy,
Type specifiedTy,
ConstraintLocator *locator) {
return new (cs.getAllocator())
IgnoreContextualType(cs, resultTy, specifiedTy, locator);
}
37 changes: 37 additions & 0 deletions lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,26 @@ class ContextualMismatch : public ConstraintFix {
ConstraintLocator *locator);
};

/// This is a contextual mismatch between throwing and non-throwing
/// function types, repair it by dropping `throws` attribute.
class DropThrowsAttribute final : public ContextualMismatch {
DropThrowsAttribute(ConstraintSystem &cs, FunctionType *fromType,
FunctionType *toType, ConstraintLocator *locator)
: ContextualMismatch(cs, fromType, toType, locator) {
assert(fromType->throws() != toType->throws());
}

public:
std::string getName() const override { return "drop 'throws' attribute"; }

bool diagnose(Expr *root, bool asNote = false) const override;

static DropThrowsAttribute *create(ConstraintSystem &cs,
FunctionType *fromType,
FunctionType *toType,
ConstraintLocator *locator);
};

/// Append 'as! T' to force a downcast to the specified type.
class ForceDowncast final : public ContextualMismatch {
ForceDowncast(ConstraintSystem &cs, Type fromType, Type toType,
Expand Down Expand Up @@ -1282,6 +1302,23 @@ class AllowTupleSplatForSingleParameter final : public ConstraintFix {
ConstraintLocatorBuilder locator);
};

class IgnoreContextualType : public ContextualMismatch {
IgnoreContextualType(ConstraintSystem &cs, Type resultTy, Type specifiedTy,
ConstraintLocator *locator)
: ContextualMismatch(cs, resultTy, specifiedTy, locator) {}

public:
std::string getName() const override {
return "ignore specified contextual type";
}

bool diagnose(Expr *root, bool asNote = false) const override;

static IgnoreContextualType *create(ConstraintSystem &cs, Type resultTy,
Type specifiedTy,
ConstraintLocator *locator);
};

} // end namespace constraints
} // end namespace swift

Expand Down
66 changes: 62 additions & 4 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1323,8 +1323,15 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
// A non-throwing function can be a subtype of a throwing function.
if (func1->throws() != func2->throws()) {
// Cannot drop 'throws'.
if (func1->throws() || kind < ConstraintKind::Subtype)
return getTypeMatchFailure(locator);
if (func1->throws() || kind < ConstraintKind::Subtype) {
if (!shouldAttemptFixes())
return getTypeMatchFailure(locator);

auto *fix = DropThrowsAttribute::create(*this, func1, func2,
getConstraintLocator(locator));
if (recordFix(fix))
return getTypeMatchFailure(locator);
}
}

// A non-@noescape function type can be a subtype of a @noescape function
Expand Down Expand Up @@ -1765,7 +1772,7 @@ ConstraintSystem::matchDeepEqualityTypes(Type type1, Type type2,

if (!recordFix(fix)) {
// Increase the solution's score for each mismtach this fixes.
increaseScore(SK_Fix, mismatches.size());
increaseScore(SK_Fix, mismatches.size() - 1);
return getTypeMatchSuccess();
}
return result;
Expand Down Expand Up @@ -2530,7 +2537,41 @@ bool ConstraintSystem::repairFailures(
auto *fix = AllowTupleTypeMismatch::create(*this, lhs, rhs,
getConstraintLocator(locator));
conversionsOrFixes.push_back(fix);
break;
}

// If either side is not yet resolved, it's too early for this fix.
if (lhs->isTypeVariableOrMember() || rhs->isTypeVariableOrMember())
break;

// If contextual type is an existential value, it's handled
// after conversion restriction is attempted.
if (rhs->isExistentialType())
break;

// TODO(diagnostics): This is a problem related to `inout` mismatch,
// in argument position, and we got here from CSDiag. Once
// argument-to-pararameter conversion failures are implemented,
// this check could be removed.
if (lhs->is<InOutType>() || rhs->is<InOutType>())
break;

// If there is a deep equality, superclass restriction
// already recorded, let's not add bother ignoring
// contextual type, because actual fix is going to
// be perform once restriction is applied.
if (llvm::any_of(conversionsOrFixes,
[](const RestrictionOrFix &entry) -> bool {
return entry.IsRestriction &&
(entry.getRestriction() ==
ConversionRestrictionKind::Superclass ||
entry.getRestriction() ==
ConversionRestrictionKind::DeepEquality);
}))
break;

conversionsOrFixes.push_back(IgnoreContextualType::create(
*this, lhs, rhs, getConstraintLocator(locator)));
break;
}

Expand Down Expand Up @@ -7103,7 +7144,6 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::RemoveAddressOf:
case FixKind::SkipSameTypeRequirement:
case FixKind::SkipSuperclassRequirement:
case FixKind::ContextualMismatch:
case FixKind::AddMissingArguments:
case FixKind::DefaultArgumentTypeMismatch:
case FixKind::SkipUnhandledConstructInFunctionBuilder:
Expand All @@ -7112,6 +7152,24 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
}

case FixKind::ContextualMismatch: {
if (recordFix(fix))
return SolutionKind::Error;

// If type produced by expression is a function type
// with result type matching contextual, it should have
// been diagnosed as "missing explicit call", let's
// increase the score to make sure that we don't impede that.
if (auto *fnType = type1->getAs<FunctionType>()) {
auto result = matchTypes(fnType->getResult(), type2, matchKind,
TMF_ApplyingFix, locator);
if (result == SolutionKind::Solved)
increaseScore(SK_Fix);
}

return SolutionKind::Solved;
}

case FixKind::UseSubscriptOperator:
case FixKind::ExplicitlyEscaping:
case FixKind::CoerceToCheckedCast:
Expand Down
37 changes: 37 additions & 0 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2737,6 +2737,43 @@ bool constraints::isAutoClosureArgument(Expr *argExpr) {
return false;
}

bool constraints::conformsToKnownProtocol(ConstraintSystem &cs, Type type,
KnownProtocolKind protocol) {
if (auto *proto = cs.TC.getProtocol(SourceLoc(), protocol))
return bool(TypeChecker::conformsToProtocol(
type, proto, cs.DC, ConformanceCheckFlags::InExpression));
return false;
}

/// Check whether given type conforms to `RawPepresentable` protocol
/// and return the witness type.
Type constraints::isRawRepresentable(ConstraintSystem &cs, Type type) {
auto &TC = cs.TC;
auto *DC = cs.DC;

auto rawReprType =
TC.getProtocol(SourceLoc(), KnownProtocolKind::RawRepresentable);
if (!rawReprType)
return Type();

auto conformance = TypeChecker::conformsToProtocol(
type, rawReprType, DC, ConformanceCheckFlags::InExpression);
if (!conformance)
return Type();

return conformance->getTypeWitnessByName(type, TC.Context.Id_RawValue);
}

Type constraints::isRawRepresentable(
ConstraintSystem &cs, Type type,
KnownProtocolKind rawRepresentableProtocol) {
Type rawTy = isRawRepresentable(cs, type);
if (!rawTy || !conformsToKnownProtocol(cs, rawTy, rawRepresentableProtocol))
return Type();

return rawTy;
}

void ConstraintSystem::generateConstraints(
SmallVectorImpl<Constraint *> &constraints, Type type,
ArrayRef<OverloadChoice> choices, DeclContext *useDC,
Expand Down
12 changes: 12 additions & 0 deletions lib/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -3922,6 +3922,18 @@ Expr *getArgumentExpr(Expr *expr, unsigned index);
// }
bool isAutoClosureArgument(Expr *argExpr);

/// Check whether type conforms to a given known protocol.
bool conformsToKnownProtocol(ConstraintSystem &cs, Type type,
KnownProtocolKind protocol);

/// Check whether given type conforms to `RawPepresentable` protocol
/// and return witness type.
Type isRawRepresentable(ConstraintSystem &cs, Type type);
/// Check whether given type conforms to a specific known kind
/// `RawPepresentable` protocol and return witness type.
Type isRawRepresentable(ConstraintSystem &cs, Type type,
KnownProtocolKind rawRepresentableProtocol);

class DisjunctionChoice {
unsigned Index;
Constraint *Choice;
Expand Down
Loading