Skip to content

Commit e14fa72

Browse files
committed
[CS] Don’t fail constraint generation for ErrorExpr or if type fails to resolve
Instead of failing constraint generation by returning `nullptr` for an `ErrorExpr` or returning a null type when a type fails to be resolved, return a fresh type variable. This allows the constraint solver to continue further and produce more meaningful diagnostics. Most importantly, it allows us to produce a solution where previously constraint generation for a syntactic element had failed, which is required to type check multi-statement closures in result builders inside the constraint system.
1 parent 341bc34 commit e14fa72

23 files changed

+144
-53
lines changed

include/swift/Sema/CSFix.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ enum class FixKind : uint8_t {
295295
/// Ignore result builder body if it has `return` statements.
296296
IgnoreResultBuilderWithReturnStmts,
297297

298+
/// Ignore `ErrorExpr` or `ErrorType` during pre-check.
299+
IgnoreInvalidASTNode,
300+
298301
/// Resolve type of `nil` by providing a contextual type.
299302
SpecifyContextualTypeForNil,
300303

@@ -707,6 +710,26 @@ class ContextualMismatch : public ConstraintFix {
707710

708711
bool diagnose(const Solution &solution, bool asNote = false) const override;
709712

713+
bool coalesceAndDiagnose(const Solution &solution,
714+
ArrayRef<ConstraintFix *> secondaryFixes,
715+
bool asNote = false) const override {
716+
// If the from type or to type is a placeholer type that corresponds to an
717+
// ErrorExpr, the issue has already been diagnosed. There's no need to
718+
// produce another diagnostic for the contextual mismatch complainting that
719+
// a type is not convertible to a placeholder type.
720+
if (auto fromPlaceholder = getFromType()->getAs<PlaceholderType>()) {
721+
if (fromPlaceholder->getOriginator().is<ErrorExpr *>()) {
722+
return true;
723+
}
724+
}
725+
if (auto toPlaceholder = getToType()->getAs<PlaceholderType>()) {
726+
if (toPlaceholder->getOriginator().is<ErrorExpr *>()) {
727+
return true;
728+
}
729+
}
730+
return ConstraintFix::coalesceAndDiagnose(solution, secondaryFixes, asNote);
731+
}
732+
710733
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override;
711734

712735
static ContextualMismatch *create(ConstraintSystem &cs, Type lhs, Type rhs,
@@ -2693,6 +2716,32 @@ class IgnoreResultBuilderWithReturnStmts final
26932716
}
26942717
};
26952718

2719+
class IgnoreInvalidASTNode final : public ConstraintFix {
2720+
IgnoreInvalidASTNode(ConstraintSystem &cs, ConstraintLocator *locator)
2721+
: IgnoreInvalidASTNode(cs, FixKind::IgnoreInvalidASTNode, locator) {}
2722+
2723+
protected:
2724+
IgnoreInvalidASTNode(ConstraintSystem &cs, FixKind kind,
2725+
ConstraintLocator *locator)
2726+
: ConstraintFix(cs, kind, locator) {}
2727+
2728+
public:
2729+
std::string getName() const override { return "ignore invalid AST node"; }
2730+
2731+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2732+
2733+
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
2734+
return diagnose(*commonFixes.front().first);
2735+
}
2736+
2737+
static IgnoreInvalidASTNode *create(ConstraintSystem &cs,
2738+
ConstraintLocator *locator);
2739+
2740+
static bool classof(ConstraintFix *fix) {
2741+
return fix->getKind() == FixKind::IgnoreInvalidASTNode;
2742+
}
2743+
};
2744+
26962745
class SpecifyContextualTypeForNil final : public ConstraintFix {
26972746
SpecifyContextualTypeForNil(ConstraintSystem &cs,
26982747
ConstraintLocator *locator)

lib/Sema/CSFix.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,6 +1945,16 @@ IgnoreInvalidResultBuilderBody::create(ConstraintSystem &cs,
19451945
return new (cs.getAllocator()) IgnoreInvalidResultBuilderBody(cs, locator);
19461946
}
19471947

1948+
bool IgnoreInvalidASTNode::diagnose(const Solution &solution,
1949+
bool asNote) const {
1950+
return true; // Already diagnosed by the producer of ErrorExpr or ErrorType.
1951+
}
1952+
1953+
IgnoreInvalidASTNode *IgnoreInvalidASTNode::create(ConstraintSystem &cs,
1954+
ConstraintLocator *locator) {
1955+
return new (cs.getAllocator()) IgnoreInvalidASTNode(cs, locator);
1956+
}
1957+
19481958
bool SpecifyContextualTypeForNil::diagnose(const Solution &solution,
19491959
bool asNote) const {
19501960
MissingContextualTypeForNil failure(solution, getLocator());

lib/Sema/CSGen.cpp

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,11 @@ namespace {
890890
// that member through the base returns a value convertible to the type
891891
// of this expression.
892892
auto baseTy = CS.getType(base);
893+
if (isa<ErrorExpr>(base)) {
894+
return CS.createTypeVariable(
895+
CS.getConstraintLocator(expr, ConstraintLocator::Member),
896+
TVO_CanBindToHole);
897+
}
893898
auto tv = CS.createTypeVariable(
894899
CS.getConstraintLocator(expr, ConstraintLocator::Member),
895900
TVO_CanBindToLValue | TVO_CanBindToNoEscape);
@@ -1068,19 +1073,11 @@ namespace {
10681073
ConstraintSystem &getConstraintSystem() const { return CS; }
10691074

10701075
virtual Type visitErrorExpr(ErrorExpr *E) {
1071-
if (!CS.isForCodeCompletion())
1072-
return nullptr;
1073-
1074-
// For code completion, treat error expressions that don't contain
1075-
// the completion location itself as holes. If an ErrorExpr contains the
1076-
// code completion location, a fallback typecheck is called on the
1077-
// ErrorExpr's OriginalExpr (valid sub-expression) if it had one,
1078-
// independent of the wider expression containing the ErrorExpr, so
1079-
// there's no point attempting to produce a solution for it.
1080-
if (CS.containsCodeCompletionLoc(E))
1081-
return nullptr;
1076+
CS.recordFix(
1077+
IgnoreInvalidASTNode::create(CS, CS.getConstraintLocator(E)));
10821078

1083-
return PlaceholderType::get(CS.getASTContext(), E);
1079+
return CS.createTypeVariable(CS.getConstraintLocator(E),
1080+
TVO_CanBindToHole);
10841081
}
10851082

10861083
virtual Type visitCodeCompletionExpr(CodeCompletionExpr *E) {
@@ -1374,7 +1371,11 @@ namespace {
13741371
const auto result = TypeResolution::resolveContextualType(
13751372
repr, CS.DC, resCtx, genericOpener, placeholderHandler);
13761373
if (result->hasError()) {
1377-
return Type();
1374+
CS.recordFix(
1375+
IgnoreInvalidASTNode::create(CS, CS.getConstraintLocator(locator)));
1376+
1377+
return CS.createTypeVariable(CS.getConstraintLocator(repr),
1378+
TVO_CanBindToHole);
13781379
}
13791380
// Diagnose top-level usages of placeholder types.
13801381
if (isa<PlaceholderTypeRepr>(repr->getWithoutParens())) {
@@ -1600,7 +1601,11 @@ namespace {
16001601

16011602
Type visitUnresolvedSpecializeExpr(UnresolvedSpecializeExpr *expr) {
16021603
auto baseTy = CS.getType(expr->getSubExpr());
1603-
1604+
1605+
if (baseTy->isTypeVariableOrMember()) {
1606+
return baseTy;
1607+
}
1608+
16041609
// We currently only support explicit specialization of generic types.
16051610
// FIXME: We could support explicit function specialization.
16061611
auto &de = CS.getASTContext().Diags;
@@ -2322,8 +2327,9 @@ namespace {
23222327
}
23232328

23242329
if (!varType) {
2325-
varType = CS.createTypeVariable(CS.getConstraintLocator(locator),
2326-
TVO_CanBindToNoEscape);
2330+
varType =
2331+
CS.createTypeVariable(CS.getConstraintLocator(locator),
2332+
TVO_CanBindToNoEscape | TVO_CanBindToHole);
23272333

23282334
// If this is either a `weak` declaration or capture e.g.
23292335
// `weak var ...` or `[weak self]`. Let's wrap type variable

lib/Sema/CSSimplify.cpp

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11211,18 +11211,6 @@ ConstraintSystem::simplifyApplicableFnConstraint(
1121111211
// following: $T1 -> $T2.
1121211212
auto func1 = type1->castTo<FunctionType>();
1121311213

11214-
// If a type variable representing "function type" is a hole
11215-
// or it could be bound to some concrete type with a help of
11216-
// a fix, let's propagate holes to the "input" type. Doing so
11217-
// provides more information to upcoming argument and result matching.
11218-
if (shouldAttemptFixes()) {
11219-
if (auto *typeVar = type2->getAs<TypeVariableType>()) {
11220-
auto *locator = typeVar->getImpl().getLocator();
11221-
if (typeVar->isPlaceholder() || hasFixFor(locator))
11222-
recordAnyTypeVarAsPotentialHole(func1);
11223-
}
11224-
}
11225-
1122611214
// Before stripping lvalue-ness and optional types, save the original second
1122711215
// type for handling `func callAsFunction` and `@dynamicCallable`
1122811216
// applications. This supports the following cases:
@@ -11237,6 +11225,29 @@ ConstraintSystem::simplifyApplicableFnConstraint(
1123711225
type2 = getFixedTypeRecursive(type2, flags, /*wantRValue=*/true);
1123811226
auto desugar2 = type2->getDesugaredType();
1123911227

11228+
// If a type variable representing "function type" is a hole
11229+
// or it could be bound to some concrete type with a help of
11230+
// a fix, let's propagate holes to the "input" type. Doing so
11231+
// provides more information to upcoming argument and result matching.
11232+
if (shouldAttemptFixes()) {
11233+
if (auto *typeVar = type2->getAs<TypeVariableType>()) {
11234+
auto *locator = typeVar->getImpl().getLocator();
11235+
if (hasFixFor(locator)) {
11236+
recordAnyTypeVarAsPotentialHole(func1);
11237+
}
11238+
}
11239+
Type underlyingType = desugar2;
11240+
while (auto *MT = underlyingType->getAs<AnyMetatypeType>()) {
11241+
underlyingType = MT->getInstanceType();
11242+
}
11243+
underlyingType =
11244+
getFixedTypeRecursive(underlyingType, flags, /*wantRValue=*/true);
11245+
if (underlyingType->isPlaceholder()) {
11246+
recordAnyTypeVarAsPotentialHole(func1);
11247+
return SolutionKind::Solved;
11248+
}
11249+
}
11250+
1124011251
TypeMatchOptions subflags = getDefaultDecompositionOptions(flags);
1124111252

1124211253
SmallVector<LocatorPathElt, 2> parts;
@@ -12927,6 +12938,9 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1292712938
case FixKind::RenameConflictingPatternVariables: {
1292812939
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
1292912940
}
12941+
case FixKind::IgnoreInvalidASTNode: {
12942+
return recordFix(fix, 10) ? SolutionKind::Error : SolutionKind::Solved;
12943+
}
1293012944

1293112945
case FixKind::ExplicitlyConstructRawRepresentable: {
1293212946
// Let's increase impact of this fix for binary operators because

test/ClangImporter/MixedSource/can_import_objc_idempotent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
// expected-error@-1 {{cannot find 'CGRect' in scope}}
3131

3232
let (r, s) = square.divided(atDistance: 50, from: .minXEdge)
33-
// expected-error@-1 {{type of expression is ambiguous without more context}}
33+
// expected-error@-1 {{cannot infer contextual base in reference to member 'minXEdge'}}
3434
#endif
3535

3636
#if canImport(MixedWithHeader)

test/Constraints/diagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@ func badTypes() {
11371137
// rdar://34357545
11381138
func unresolvedTypeExistential() -> Bool {
11391139
return (Int.self==_{})
1140-
// expected-error@-1 {{type of expression is ambiguous without more context}}
1140+
// expected-error@-1 {{could not infer type for placeholder}}
11411141
// expected-error@-2 {{type placeholder not allowed here}}
11421142
}
11431143

test/IDE/complete_repl_identifier_prefix_1.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// RUN: %target-swift-ide-test -repl-code-completion -source-filename %s | %FileCheck %s
22

33
// CHECK: Begin completions
4+
// CHECK-NEXT: .self: _
45
// CHECK-NEXT: {{^}}true: Bool{{$}}
56
// CHECK-NEXT: End completions
67

test/Parse/confusables.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ let number⁚ Int // expected-note {{operator '⁚' (Two Dot Punctuation) looks
1111
// expected-error @+1 {{consecutive statements on a line must be separated by ';'}}
1212
55 // expected-note {{unicode character '‒' (Figure Dash) looks similar to '-' (Hyphen Minus); did you mean to use '-' (Hyphen Minus)?}} {{3-6=-}}
1313

14+
// expected-error @+3 {{cannot convert value of type '(Bool, _)' to expected condition type 'Bool'}}
1415
// expected-error @+2 {{cannot find 'ꝸꝸꝸ' in scope}}
1516
// expected-error @+1 {{expected ',' separator}}
1617
if (true ꝸꝸꝸ false) {} // expected-note {{identifier 'ꝸꝸꝸ' contains possibly confused characters; did you mean to use '&&&'?}} {{10-19=&&&}}

test/Parse/invalid.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ func runAction() {} // expected-note {{'runAction' declared here}}
2525

2626
// rdar://16601779
2727
func foo() {
28-
runAction(SKAction.sequence() // expected-error {{cannot find 'SKAction' in scope; did you mean 'runAction'?}} {{13-21=runAction}} expected-error {{expected ',' separator}} {{32-32=,}}
28+
// expected-error@+3 {{argument passed to call that takes no arguments}}
29+
// expected-error@+2 {{cannot find 'SKAction' in scope; did you mean 'runAction'?}} {{13-21=runAction}}
30+
// expected-error@+1 {{expected ',' separator}} {{32-32=,}}
31+
runAction(SKAction.sequence()
2932

3033
skview!
3134
// expected-error @-1 {{cannot find 'skview' in scope}}

test/Parse/matching_patterns.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,4 @@ case (_?)?: break // expected-warning {{case is already handled by previous patt
329329
let (responseObject: Int?) = op1
330330
// expected-error @-1 {{expected ',' separator}} {{25-25=,}}
331331
// expected-error @-2 {{expected pattern}}
332-
// expected-error @-3 {{type of expression is ambiguous without more context}}
332+
// expected-error @-3 {{cannot convert value of type 'Int?' to specified type '(responseObject: _)'}}

0 commit comments

Comments
 (0)