Skip to content

Commit d8234ba

Browse files
committed
Sema: Fix double type checking of lazy initializer expressions
If a lazy var has no declared type, we have to type check the initializer to get a type before we can build the getter. Then, the initializer is type checked as part of the getter again. Use the new SkipApplyingSolution flag when type checking for the first time. We still end up doing redundant work, but by not applying the solution we avoid feeding invalid AST nodes back into the constraint solver. This fixes some bad diagnostics and crashes. Fixes <https://bugs.swift.org/browse/SR-2616> and <rdar://problem/28313602>.
1 parent fa155bf commit d8234ba

8 files changed

+59
-41
lines changed

lib/Sema/CSApply.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7581,12 +7581,11 @@ Expr *ConstraintSystem::applySolutionShallow(const Solution &solution,
75817581
Expr *Solution::coerceToType(Expr *expr, Type toType,
75827582
ConstraintLocator *locator,
75837583
bool ignoreTopLevelInjection,
7584-
bool skipClosures,
75857584
Optional<Pattern*> typeFromPattern) const {
75867585
auto &cs = getConstraintSystem();
75877586
ExprRewriter rewriter(cs, *this,
75887587
/*suppressDiagnostics=*/false,
7589-
/*skipClosures=*/skipClosures);
7588+
/*skipClosures=*/false);
75907589
Expr *result = rewriter.coerceToType(expr, toType, locator, typeFromPattern);
75917590
if (!result)
75927591
return nullptr;

lib/Sema/ConstraintSystem.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -575,17 +575,13 @@ class Solution {
575575
/// on a suspicious top-level optional injection (because the caller already
576576
/// diagnosed it).
577577
///
578-
/// \param skipClosures Whether to skip bodies of non-single expression
579-
/// closures.
580-
///
581578
/// \param typeFromPattern Optionally, the caller can specify the pattern
582579
/// from where the toType is derived, so that we can deliver better fixit.
583580
///
584581
/// \returns the coerced expression, which will have type \c ToType.
585582
Expr *coerceToType(Expr *expr, Type toType,
586583
ConstraintLocator *locator,
587584
bool ignoreTopLevelInjection = false,
588-
bool skipClosures = false,
589585
Optional<Pattern*> typeFromPattern = None) const;
590586

591587
/// \brief Convert the given expression to a logic value.

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2125,13 +2125,12 @@ bool TypeChecker::typeCheckExpressionShallow(Expr *&expr, DeclContext *dc) {
21252125
}
21262126

21272127
bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
2128-
DeclContext *DC, bool skipClosures) {
2128+
DeclContext *DC, bool skipApplyingSolution) {
21292129

21302130
/// Type checking listener for pattern binding initializers.
21312131
class BindingListener : public ExprTypeCheckListener {
21322132
Pattern *&pattern;
21332133
Expr *&initializer;
2134-
bool skipClosures;
21352134

21362135
/// The locator we're using.
21372136
ConstraintLocator *Locator;
@@ -2140,10 +2139,9 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
21402139
Type InitType;
21412140

21422141
public:
2143-
explicit BindingListener(Pattern *&pattern, Expr *&initializer,
2144-
bool skipClosures)
2142+
explicit BindingListener(Pattern *&pattern, Expr *&initializer)
21452143
: pattern(pattern), initializer(initializer),
2146-
skipClosures(skipClosures), Locator(nullptr) { }
2144+
Locator(nullptr) { }
21472145

21482146
Type getInitType() const { return InitType; }
21492147

@@ -2177,8 +2175,7 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
21772175
// Convert the initializer to the type of the pattern.
21782176
// ignoreTopLevelInjection = Binding->isConditional()
21792177
expr = solution.coerceToType(expr, InitType, Locator,
2180-
false /* ignoreTopLevelInjection */,
2181-
skipClosures);
2178+
false /* ignoreTopLevelInjection */);
21822179
if (!expr) {
21832180
return nullptr;
21842181
}
@@ -2191,7 +2188,7 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
21912188
};
21922189

21932190
assert(initializer && "type-checking an uninitialized binding?");
2194-
BindingListener listener(pattern, initializer, skipClosures);
2191+
BindingListener listener(pattern, initializer);
21952192

21962193
TypeLoc contextualType;
21972194
auto contextualPurpose = CTP_Unused;
@@ -2213,8 +2210,8 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
22132210

22142211
// Type-check the initializer.
22152212
TypeCheckExprOptions flags = TypeCheckExprFlags::ConvertTypeIsOnlyAHint;
2216-
if (skipClosures)
2217-
flags |= TypeCheckExprFlags::SkipMultiStmtClosures;
2213+
if (skipApplyingSolution)
2214+
flags |= TypeCheckExprFlags::SkipApplyingSolution;
22182215

22192216
bool hadError = typeCheckExpression(initializer, DC, contextualType,
22202217
contextualPurpose,
@@ -2262,7 +2259,7 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
22622259

22632260
bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD,
22642261
unsigned patternNumber,
2265-
bool skipClosures) {
2262+
bool skipApplyingSolution) {
22662263

22672264
Pattern *pattern = PBD->getPattern(patternNumber);
22682265
Expr *init = PBD->getInit(patternNumber);
@@ -2282,11 +2279,10 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD,
22822279
DC = initContext;
22832280
}
22842281

2285-
bool hadError = typeCheckBinding(pattern, init, DC, skipClosures);
2282+
bool hadError = typeCheckBinding(pattern, init, DC, skipApplyingSolution);
22862283
PBD->setPattern(patternNumber, pattern, initContext);
22872284
PBD->setInit(patternNumber, init);
22882285

2289-
22902286
// If we entered an initializer context, contextualize any
22912287
// auto-closures we might have created.
22922288
if (initContext) {
@@ -2620,7 +2616,8 @@ bool TypeChecker::typeCheckStmtCondition(StmtCondition &cond, DeclContext *dc,
26202616
// If the pattern didn't get a type, it's because we ran into some
26212617
// unknown types along the way. We'll need to check the initializer.
26222618
auto init = elt.getInitializer();
2623-
hadError |= typeCheckBinding(pattern, init, dc, /*skipClosures*/false);
2619+
hadError |= typeCheckBinding(pattern, init, dc,
2620+
/*skipApplyingSolution*/false);
26242621
elt.setPattern(pattern);
26252622
elt.setInitializer(init);
26262623
hadAnyFalsable |= pattern->isRefutablePattern();
@@ -2937,7 +2934,6 @@ bool TypeChecker::convertToType(Expr *&expr, Type type, DeclContext *dc,
29372934
Expr *result = solution.coerceToType(expr, type,
29382935
cs.getConstraintLocator(expr),
29392936
/*ignoreTopLevelInjection*/false,
2940-
/*skipClosures*/false,
29412937
typeFromPattern);
29422938
if (!result) {
29432939
return true;

lib/Sema/TypeCheckDecl.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,11 +1198,11 @@ static void validatePatternBindingEntry(TypeChecker &tc,
11981198
// If the pattern didn't get a type or if it contains an unbound generic type,
11991199
// we'll need to check the initializer.
12001200
if (!pattern->hasType() || pattern->getType()->hasUnboundGenericType()) {
1201-
bool skipClosures = false;
1201+
bool skipApplyingSolution = false;
12021202
if (auto var = binding->getSingleVar())
1203-
skipClosures = var->getAttrs().hasAttribute<LazyAttr>();
1203+
skipApplyingSolution = var->getAttrs().hasAttribute<LazyAttr>();
12041204

1205-
if (tc.typeCheckPatternBinding(binding, entryNumber, skipClosures))
1205+
if (tc.typeCheckPatternBinding(binding, entryNumber, skipApplyingSolution))
12061206
return;
12071207
}
12081208

@@ -4047,7 +4047,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
40474047
if (!IsFirstPass) {
40484048
for (unsigned i = 0, e = PBD->getNumPatternEntries(); i != e; ++i) {
40494049
if (!PBD->isInitializerChecked(i) && PBD->getInit(i))
4050-
TC.typeCheckPatternBinding(PBD, i, /*skipClosures*/false);
4050+
TC.typeCheckPatternBinding(PBD, i, /*skipApplyingSolution*/false);
40514051
}
40524052
}
40534053

@@ -4078,7 +4078,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
40784078
// If we got a default initializer, install it and re-type-check it
40794079
// to make sure it is properly coerced to the pattern type.
40804080
PBD->setInit(i, defaultInit);
4081-
TC.typeCheckPatternBinding(PBD, i, /*skipClosures*/false);
4081+
TC.typeCheckPatternBinding(PBD, i, /*skipApplyingSolution*/false);
40824082
}
40834083
}
40844084
}

lib/Sema/TypeChecker.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,9 +1677,9 @@ class TypeChecker final : public LazyResolver {
16771677

16781678
/// Type-check an initialized variable pattern declaration.
16791679
bool typeCheckBinding(Pattern *&P, Expr *&Init, DeclContext *DC,
1680-
bool skipClosures);
1680+
bool skipApplyingSolution);
16811681
bool typeCheckPatternBinding(PatternBindingDecl *PBD, unsigned patternNumber,
1682-
bool skipClosures);
1682+
bool skipApplyingSolution);
16831683

16841684
/// Type-check a for-each loop's pattern binding and sequence together.
16851685
bool typeCheckForEachBinding(DeclContext *dc, ForEachStmt *stmt);

test/decl/var/lazy_properties.swift

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,20 @@ class TestClass {
2727

2828
lazy var (e, f) = (1,2) // expected-error {{'lazy' cannot destructure an initializer}} {{3-8=}}
2929

30-
lazy var g : Int = { 0 }() // single-expr closure
30+
lazy var g = { 0 }() // single-expr closure
3131

32-
lazy var h : Int = { return 0 }()+1 // multi-stmt closure
32+
lazy var h : Int = { 0 }() // single-expr closure
3333

34-
lazy var i : Int = 42 { // expected-error {{lazy properties may not have observers}} {{3-8=}}
35-
didSet {
36-
}
37-
}
34+
lazy var i = { () -> Int in return 0 }()+1 // multi-stmt closure
35+
36+
lazy var j : Int = { return 0 }()+1 // multi-stmt closure
37+
38+
lazy var k : Int = { () -> Int in return 0 }()+1 // multi-stmt closure
3839

39-
// Lazy values can have observers, be NSCopying, etc.
40-
/* lazy var d : Int = 42 {
40+
lazy var l : Int = 42 { // expected-error {{lazy properties may not have observers}} {{3-8=}}
4141
didSet {
42-
print("set me")
4342
}
44-
}*/
43+
}
4544

4645
init() {
4746
lazy var localvar = 42 // expected-error {{lazy is only valid for members of a struct or class}} {{5-10=}}
@@ -91,3 +90,32 @@ class WeShouldNotReTypeCheckStatements {
9190
_ = ()
9291
}
9392
}
93+
94+
protocol MyProtocol {
95+
func f() -> Int
96+
}
97+
98+
struct MyStruct : MyProtocol {
99+
func f() -> Int { return 0 }
100+
}
101+
102+
struct Outer {
103+
static let p: MyProtocol = MyStruct()
104+
105+
struct Inner {
106+
lazy var x = p.f()
107+
108+
lazy var y = {_ = 3}()
109+
// expected-warning@-1 {{variable 'y' inferred to have type '()', which may be unexpected}}
110+
// expected-note@-2 {{add an explicit type annotation to silence this warning}}
111+
}
112+
}
113+
114+
// https://bugs.swift.org/browse/SR-2616
115+
struct Construction {
116+
init(x: Int, y: Int? = 42) { }
117+
}
118+
119+
class Constructor {
120+
lazy var myQ = Construction(x: 3)
121+
}

validation-test/compiler_crashers/28508-unreachable-executed-at-swift-lib-sema-csgen-cpp-2656.swift renamed to validation-test/compiler_crashers_fixed/28508-unreachable-executed-at-swift-lib-sema-csgen-cpp-2656.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
// See https://swift.org/LICENSE.txt for license information
66
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
77

8-
// RUN: not --crash %target-swift-frontend %s -emit-ir
8+
// RUN: not %target-swift-frontend %s -emit-ir
99
protocol A{{}func a{}struct A{lazy var f=a
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@
55
// See https://swift.org/LICENSE.txt for license information
66
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
77

8-
// RUN: not --crash %target-swift-frontend %s -emit-ir
9-
// REQUIRES: asserts
8+
// RUN: not %target-swift-frontend %s -emit-ir
109
class d{lazy var f={_={

0 commit comments

Comments
 (0)