Skip to content

Lazier type checking of lazy vars #9916

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
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7581,12 +7581,11 @@ Expr *ConstraintSystem::applySolutionShallow(const Solution &solution,
Expr *Solution::coerceToType(Expr *expr, Type toType,
ConstraintLocator *locator,
bool ignoreTopLevelInjection,
bool skipClosures,
Optional<Pattern*> typeFromPattern) const {
auto &cs = getConstraintSystem();
ExprRewriter rewriter(cs, *this,
/*suppressDiagnostics=*/false,
/*skipClosures=*/skipClosures);
/*skipClosures=*/false);
Expr *result = rewriter.coerceToType(expr, toType, locator, typeFromPattern);
if (!result)
return nullptr;
Expand Down
4 changes: 0 additions & 4 deletions lib/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -575,17 +575,13 @@ class Solution {
/// on a suspicious top-level optional injection (because the caller already
/// diagnosed it).
///
/// \param skipClosures Whether to skip bodies of non-single expression
/// closures.
///
/// \param typeFromPattern Optionally, the caller can specify the pattern
/// from where the toType is derived, so that we can deliver better fixit.
///
/// \returns the coerced expression, which will have type \c ToType.
Expr *coerceToType(Expr *expr, Type toType,
ConstraintLocator *locator,
bool ignoreTopLevelInjection = false,
bool skipClosures = false,
Optional<Pattern*> typeFromPattern = None) const;

/// \brief Convert the given expression to a logic value.
Expand Down
104 changes: 60 additions & 44 deletions lib/Sema/TypeCheckConstraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,10 @@ bool ExprTypeCheckListener::builtConstraints(ConstraintSystem &cs, Expr *expr) {
return false;
}

Expr *ExprTypeCheckListener::foundSolution(Solution &solution, Expr *expr) {
return expr;
}

Expr *ExprTypeCheckListener::appliedSolution(Solution &solution, Expr *expr) {
return expr;
}
Expand Down Expand Up @@ -1864,14 +1868,24 @@ bool TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
expr->setType(ErrorType::get(Context));
return false;
}
// Apply the solution to the expression.

auto result = expr;
auto &solution = viable[0];
if (listener) {
result = listener->foundSolution(solution, result);
if (!result)
return true;
}

if (options.contains(TypeCheckExprFlags::SkipApplyingSolution))
return false;

// Apply the solution to the expression.
bool isDiscarded = options.contains(TypeCheckExprFlags::IsDiscarded);
bool skipClosures = options.contains(TypeCheckExprFlags::SkipMultiStmtClosures);
auto result = cs.applySolution(solution, expr, convertType.getType(),
isDiscarded, suppressDiagnostics,
skipClosures);
result = cs.applySolution(solution, result, convertType.getType(),
isDiscarded, suppressDiagnostics,
skipClosures);
if (!result) {
// Failure already diagnosed, above, as part of applying the solution.
return true;
Expand Down Expand Up @@ -2111,26 +2125,25 @@ bool TypeChecker::typeCheckExpressionShallow(Expr *&expr, DeclContext *dc) {
}

bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
DeclContext *DC, bool skipClosures) {
DeclContext *DC, bool skipApplyingSolution) {

/// Type checking listener for pattern binding initializers.
class BindingListener : public ExprTypeCheckListener {
Pattern *&pattern;
Expr *&initializer;
DeclContext *DC;
bool skipClosures;

/// The locator we're using.
ConstraintLocator *Locator;

/// The type of the initializer.
Type InitType;

public:
explicit BindingListener(Pattern *&pattern, Expr *&initializer,
DeclContext *DC, bool skipClosures)
: pattern(pattern), initializer(initializer), DC(DC),
skipClosures(skipClosures) { }
explicit BindingListener(Pattern *&pattern, Expr *&initializer)
: pattern(pattern), initializer(initializer),
Locator(nullptr) { }

Type getInitType() const { return InitType; }

bool builtConstraints(ConstraintSystem &cs, Expr *expr) override {
// Save the locator we're using for the expression.
Expand All @@ -2150,44 +2163,32 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
return false;
}

Expr *appliedSolution(Solution &solution, Expr *expr) override {
Expr *foundSolution(Solution &solution, Expr *expr) override {
// Figure out what type the constraints decided on.
auto &cs = solution.getConstraintSystem();
auto &tc = cs.getTypeChecker();
InitType = solution.simplifyType(InitType);

// Just keep going.
return expr;
}

Expr *appliedSolution(Solution &solution, Expr *expr) override {
// Convert the initializer to the type of the pattern.
// ignoreTopLevelInjection = Binding->isConditional()
expr = solution.coerceToType(expr, InitType, Locator,
false /* ignoreTopLevelInjection */,
skipClosures);
false /* ignoreTopLevelInjection */);
if (!expr) {
return nullptr;
}

// Force the initializer to be materializable.
// FIXME: work this into the constraint system
expr = tc.coerceToMaterializable(expr);
assert(expr->getType()->isEqual(InitType));

// Apply the solution to the pattern as well.
Type patternType = expr->getType();

TypeResolutionOptions options;
options |= TR_OverrideType;
options |= TR_InExpression;
if (isa<EditorPlaceholderExpr>(expr->getSemanticsProvidingExpr())) {
options |= TR_EditorPlaceholder;
}
if (tc.coercePatternToType(pattern, DC, patternType, options)) {
return nullptr;
}
initializer = expr;
return expr;
}
};

assert(initializer && "type-checking an uninitialized binding?");
BindingListener listener(pattern, initializer, DC, skipClosures);
BindingListener listener(pattern, initializer);

TypeLoc contextualType;
auto contextualPurpose = CTP_Unused;
Expand All @@ -2209,18 +2210,34 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,

// Type-check the initializer.
TypeCheckExprOptions flags = TypeCheckExprFlags::ConvertTypeIsOnlyAHint;
if (skipClosures)
flags |= TypeCheckExprFlags::SkipMultiStmtClosures;
if (skipApplyingSolution)
flags |= TypeCheckExprFlags::SkipApplyingSolution;

bool hadError = typeCheckExpression(initializer, DC, contextualType,
contextualPurpose,
flags,
&listener);

if (hadError && !initializer->getType()) {
initializer->setType(ErrorType::get(Context));

if (!hadError) {
TypeResolutionOptions options;
options |= TR_OverrideType;
options |= TR_InExpression;
if (isa<EditorPlaceholderExpr>(initializer->getSemanticsProvidingExpr())) {
options |= TR_EditorPlaceholder;
}

// Apply the solution to the pattern as well.
if (coercePatternToType(pattern, DC, listener.getInitType(), options)) {
return true;
}
}

if (hadError && !initializer->getType())
initializer->setType(ErrorType::get(Context));

// If the type of the pattern is inferred, assign error types to the pattern
// and its variables, to prevent it from being referenced by the constraint
// system.
if (hadError &&
(!pattern->hasType() ||
pattern->getType()->hasUnboundGenericType())) {
Expand All @@ -2242,7 +2259,7 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,

bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD,
unsigned patternNumber,
bool skipClosures) {
bool skipApplyingSolution) {

Pattern *pattern = PBD->getPattern(patternNumber);
Expr *init = PBD->getInit(patternNumber);
Expand All @@ -2262,11 +2279,10 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD,
DC = initContext;
}

bool hadError = typeCheckBinding(pattern, init, DC, skipClosures);
bool hadError = typeCheckBinding(pattern, init, DC, skipApplyingSolution);
PBD->setPattern(patternNumber, pattern, initContext);
PBD->setInit(patternNumber, init);


// If we entered an initializer context, contextualize any
// auto-closures we might have created.
if (initContext) {
Expand Down Expand Up @@ -2600,7 +2616,8 @@ bool TypeChecker::typeCheckStmtCondition(StmtCondition &cond, DeclContext *dc,
// If the pattern didn't get a type, it's because we ran into some
// unknown types along the way. We'll need to check the initializer.
auto init = elt.getInitializer();
hadError |= typeCheckBinding(pattern, init, dc, /*skipClosures*/false);
hadError |= typeCheckBinding(pattern, init, dc,
/*skipApplyingSolution*/false);
elt.setPattern(pattern);
elt.setInitializer(init);
hadAnyFalsable |= pattern->isRefutablePattern();
Expand Down Expand Up @@ -2917,7 +2934,6 @@ bool TypeChecker::convertToType(Expr *&expr, Type type, DeclContext *dc,
Expr *result = solution.coerceToType(expr, type,
cs.getConstraintLocator(expr),
/*ignoreTopLevelInjection*/false,
/*skipClosures*/false,
typeFromPattern);
if (!result) {
return true;
Expand Down
10 changes: 5 additions & 5 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1198,11 +1198,11 @@ static void validatePatternBindingEntry(TypeChecker &tc,
// If the pattern didn't get a type or if it contains an unbound generic type,
// we'll need to check the initializer.
if (!pattern->hasType() || pattern->getType()->hasUnboundGenericType()) {
bool skipClosures = false;
bool skipApplyingSolution = false;
if (auto var = binding->getSingleVar())
skipClosures = var->getAttrs().hasAttribute<LazyAttr>();
skipApplyingSolution = var->getAttrs().hasAttribute<LazyAttr>();

if (tc.typeCheckPatternBinding(binding, entryNumber, skipClosures))
if (tc.typeCheckPatternBinding(binding, entryNumber, skipApplyingSolution))
return;
}

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

Expand Down Expand Up @@ -4078,7 +4078,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
// If we got a default initializer, install it and re-type-check it
// to make sure it is properly coerced to the pattern type.
PBD->setInit(i, defaultInit);
TC.typeCheckPatternBinding(PBD, i, /*skipClosures*/false);
TC.typeCheckPatternBinding(PBD, i, /*skipApplyingSolution*/false);
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ enum class TypeCheckExprFlags {
/// Set if the client prefers fixits to be in the form of force unwrapping
/// or optional chaining to return an optional.
PreferForceUnwrapToOptional = 0x80,

/// If set, don't apply a solution.
SkipApplyingSolution = 0x100,
};

typedef OptionSet<TypeCheckExprFlags> TypeCheckExprOptions;
Expand Down Expand Up @@ -325,6 +328,13 @@ class ExprTypeCheckListener {
/// constraint system, or false otherwise.
virtual bool builtConstraints(constraints::ConstraintSystem &cs, Expr *expr);

/// Callback invoked once a solution has been found.
///
/// The callback may further alter the expression, returning either a
/// new expression (to replace the result) or a null pointer to indicate
/// failure.
virtual Expr *foundSolution(constraints::Solution &solution, Expr *expr);

/// Callback invokes once the chosen solution has been applied to the
/// expression.
///
Expand Down Expand Up @@ -1667,9 +1677,9 @@ class TypeChecker final : public LazyResolver {

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

/// Type-check a for-each loop's pattern binding and sequence together.
bool typeCheckForEachBinding(DeclContext *dc, ForEachStmt *stmt);
Expand Down
4 changes: 2 additions & 2 deletions test/Constraints/array_literal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,15 @@ func defaultToAny(i: Int, s: String) {

let a2: Array = [1, "a", 3.5]
// expected-error@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}
let _: Int = a2 // expected-error{{value of type '[Any]'}}
let _: Int = a2 // expected-error{{value of type 'Array<Any>'}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the diagnostics changes here seem like improvements, but not this and the one below. Any idea why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because we declare 'let a2: Array', so we're only inferring generic arguments here and not the whole type.


let a3 = [1, "a", nil, 3.5]
// expected-error@-1{{heterogeneous collection literal could only be inferred to '[Any?]'; add explicit type annotation if this is intentional}}
let _: Int = a3 // expected-error{{value of type '[Any?]'}}

let a4: Array = [1, "a", nil, 3.5]
// expected-error@-1{{heterogeneous collection literal could only be inferred to '[Any?]'; add explicit type annotation if this is intentional}}
let _: Int = a4 // expected-error{{value of type '[Any?]'}}
let _: Int = a4 // expected-error{{value of type 'Array<Any?>'}}

let a5 = []
// expected-error@-1{{empty collection literal requires an explicit type}}
Expand Down
8 changes: 4 additions & 4 deletions test/IDE/print_types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ func testVariableTypes(_ param: Int, param2: inout Double) {
_ = typealias1 ; typealias1 = 1

var optional1 = Optional<Int>.none
// CHECK: VarDecl '''optional1''' Optional<Int>{{$}}
// FULL: VarDecl '''optional1''' Swift.Optional<Swift.Int>{{$}}
// CHECK: VarDecl '''optional1''' Int?{{$}}
// FULL: VarDecl '''optional1''' Swift.Int?{{$}}
_ = optional1 ; optional1 = nil

var optional2 = Optional<[Int]>.none
_ = optional2 ; optional2 = nil
// CHECK: VarDecl '''optional2''' Optional<[Int]>{{$}}
// FULL: VarDecl '''optional2''' Swift.Optional<[Swift.Int]>{{$}}
// CHECK: VarDecl '''optional2''' [Int]?{{$}}
// FULL: VarDecl '''optional2''' [Swift.Int]?{{$}}
}

func testFuncType1() {}
Expand Down
2 changes: 1 addition & 1 deletion test/Interpreter/repl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ for c in "foobar".unicodeScalars { print(c) }
// CHECK-NEXT: r

var vec = Array<String>()
// CHECK: vec : Array<String> = []
// CHECK: vec : [String] = []

// Error recovery
var a : [int]
Expand Down
Loading