Skip to content

Commit 194aa4f

Browse files
authored
Merge pull request swiftlang#36582 from ahoppen/pr/rdar75200217
[CodeComplete] Fix cyclic dependency error in request evaluator during code completion
2 parents 74b5eb7 + 7e2898c commit 194aa4f

File tree

4 files changed

+158
-84
lines changed

4 files changed

+158
-84
lines changed

lib/Sema/TypeCheckDecl.cpp

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,13 +2503,38 @@ NamingPatternRequest::evaluate(Evaluator &evaluator, VarDecl *VD) const {
25032503
}
25042504

25052505
if (!namingPattern) {
2506-
// Try type checking parent control statement.
25072506
if (auto parentStmt = VD->getParentPatternStmt()) {
2508-
if (auto CS = dyn_cast<CaseStmt>(parentStmt))
2509-
parentStmt = CS->getParentStmt();
2510-
ASTNode node(parentStmt);
2511-
TypeChecker::typeCheckASTNode(node, VD->getDeclContext(),
2512-
/*LeaveBodyUnchecked=*/true);
2507+
// Try type checking parent control statement.
2508+
if (auto condStmt = dyn_cast<LabeledConditionalStmt>(parentStmt)) {
2509+
// The VarDecl is defined inside a condition of a `if` or `while` stmt.
2510+
// Only type check the condition we care about: the one with the VarDecl
2511+
bool foundVarDecl = false;
2512+
for (auto &condElt : condStmt->getCond()) {
2513+
if (auto pat = condElt.getPatternOrNull()) {
2514+
if (!pat->containsVarDecl(VD)) {
2515+
continue;
2516+
}
2517+
// We found the condition that declares the variable. Type check it
2518+
// and stop the loop. The variable can only be declared once.
2519+
2520+
// We don't care about isFalsable
2521+
bool isFalsable = false;
2522+
TypeChecker::typeCheckStmtConditionElement(condElt, isFalsable,
2523+
VD->getDeclContext());
2524+
2525+
foundVarDecl = true;
2526+
break;
2527+
}
2528+
}
2529+
assert(foundVarDecl && "VarDecl not declared in its parent?");
2530+
} else {
2531+
// We have some other parent stmt. Type check it completely.
2532+
if (auto CS = dyn_cast<CaseStmt>(parentStmt))
2533+
parentStmt = CS->getParentStmt();
2534+
ASTNode node(parentStmt);
2535+
TypeChecker::typeCheckASTNode(node, VD->getDeclContext(),
2536+
/*LeaveBodyUnchecked=*/true);
2537+
}
25132538
namingPattern = VD->getCanonicalVarDecl()->NamingPattern;
25142539
}
25152540
}

lib/Sema/TypeCheckStmt.cpp

Lines changed: 85 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,89 @@ static LabeledStmt *findBreakOrContinueStmtTarget(
419419
return nullptr;
420420
}
421421

422+
bool TypeChecker::typeCheckStmtConditionElement(StmtConditionElement &elt,
423+
bool &isFalsable,
424+
DeclContext *dc) {
425+
auto &Context = dc->getASTContext();
426+
if (elt.getKind() == StmtConditionElement::CK_Availability) {
427+
isFalsable = true;
428+
429+
// Reject inlinable code using availability macros.
430+
PoundAvailableInfo *info = elt.getAvailability();
431+
if (auto *decl = dc->getAsDecl()) {
432+
if (decl->getAttrs().hasAttribute<InlinableAttr>() ||
433+
decl->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
434+
for (auto queries : info->getQueries())
435+
if (auto availSpec =
436+
dyn_cast<PlatformVersionConstraintAvailabilitySpec>(queries))
437+
if (availSpec->getMacroLoc().isValid()) {
438+
Context.Diags.diagnose(
439+
availSpec->getMacroLoc(),
440+
swift::diag::availability_macro_in_inlinable,
441+
decl->getDescriptiveKind());
442+
break;
443+
}
444+
}
445+
446+
return false;
447+
}
448+
449+
if (auto E = elt.getBooleanOrNull()) {
450+
assert(!E->getType() && "the bool condition is already type checked");
451+
bool hadError = TypeChecker::typeCheckCondition(E, dc);
452+
elt.setBoolean(E);
453+
isFalsable = true;
454+
return hadError;
455+
}
456+
assert(elt.getKind() != StmtConditionElement::CK_Boolean);
457+
458+
// This is cleanup goop run on the various paths where type checking of the
459+
// pattern binding fails.
460+
auto typeCheckPatternFailed = [&] {
461+
elt.getPattern()->setType(ErrorType::get(Context));
462+
elt.getInitializer()->setType(ErrorType::get(Context));
463+
464+
elt.getPattern()->forEachVariable([&](VarDecl *var) {
465+
// Don't change the type of a variable that we've been able to
466+
// compute a type for.
467+
if (var->hasInterfaceType() && !var->isInvalid())
468+
return;
469+
var->setInvalid();
470+
});
471+
};
472+
473+
// Resolve the pattern.
474+
assert(!elt.getPattern()->hasType() &&
475+
"the pattern binding condition is already type checked");
476+
auto *pattern = TypeChecker::resolvePattern(elt.getPattern(), dc,
477+
/*isStmtCondition*/ true);
478+
if (!pattern) {
479+
typeCheckPatternFailed();
480+
return true;
481+
}
482+
elt.setPattern(pattern);
483+
484+
TypeChecker::diagnoseDuplicateBoundVars(pattern);
485+
486+
// Check the pattern, it allows unspecified types because the pattern can
487+
// provide type information.
488+
auto contextualPattern = ContextualPattern::forRawPattern(pattern, dc);
489+
Type patternType = TypeChecker::typeCheckPattern(contextualPattern);
490+
if (patternType->hasError()) {
491+
typeCheckPatternFailed();
492+
return true;
493+
}
494+
495+
// If the pattern didn't get a type, it's because we ran into some
496+
// unknown types along the way. We'll need to check the initializer.
497+
auto init = elt.getInitializer();
498+
bool hadError = TypeChecker::typeCheckBinding(pattern, init, dc, patternType);
499+
elt.setPattern(pattern);
500+
elt.setInitializer(init);
501+
isFalsable |= pattern->isRefutablePattern();
502+
return hadError;
503+
}
504+
422505
/// Type check the given 'if', 'while', or 'guard' statement condition.
423506
///
424507
/// \param stmt The conditional statement to type-check, which will be modified
@@ -427,88 +510,12 @@ static LabeledStmt *findBreakOrContinueStmtTarget(
427510
/// \returns true if an error occurred, false otherwise.
428511
static bool typeCheckConditionForStatement(LabeledConditionalStmt *stmt,
429512
DeclContext *dc) {
430-
auto &Context = dc->getASTContext();
431513
bool hadError = false;
432514
bool hadAnyFalsable = false;
433515
auto cond = stmt->getCond();
434516
for (auto &elt : cond) {
435-
if (elt.getKind() == StmtConditionElement::CK_Availability) {
436-
hadAnyFalsable = true;
437-
438-
// Reject inlinable code using availability macros.
439-
PoundAvailableInfo *info = elt.getAvailability();
440-
if (auto *decl = dc->getAsDecl()) {
441-
if (decl->getAttrs().hasAttribute<InlinableAttr>() ||
442-
decl->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
443-
for (auto queries : info->getQueries())
444-
if (auto availSpec =
445-
dyn_cast<PlatformVersionConstraintAvailabilitySpec>(queries))
446-
if (availSpec->getMacroLoc().isValid()) {
447-
Context.Diags.diagnose(
448-
availSpec->getMacroLoc(),
449-
swift::diag::availability_macro_in_inlinable,
450-
decl->getDescriptiveKind());
451-
break;
452-
}
453-
}
454-
455-
continue;
456-
}
457-
458-
if (auto E = elt.getBooleanOrNull()) {
459-
assert(!E->getType() && "the bool condition is already type checked");
460-
hadError |= TypeChecker::typeCheckCondition(E, dc);
461-
elt.setBoolean(E);
462-
hadAnyFalsable = true;
463-
continue;
464-
}
465-
assert(elt.getKind() != StmtConditionElement::CK_Boolean);
466-
467-
// This is cleanup goop run on the various paths where type checking of the
468-
// pattern binding fails.
469-
auto typeCheckPatternFailed = [&] {
470-
hadError = true;
471-
elt.getPattern()->setType(ErrorType::get(Context));
472-
elt.getInitializer()->setType(ErrorType::get(Context));
473-
474-
elt.getPattern()->forEachVariable([&](VarDecl *var) {
475-
// Don't change the type of a variable that we've been able to
476-
// compute a type for.
477-
if (var->hasInterfaceType() && !var->isInvalid())
478-
return;
479-
var->setInvalid();
480-
});
481-
};
482-
483-
// Resolve the pattern.
484-
assert(!elt.getPattern()->hasType() &&
485-
"the pattern binding condition is already type checked");
486-
auto *pattern = TypeChecker::resolvePattern(elt.getPattern(), dc,
487-
/*isStmtCondition*/ true);
488-
if (!pattern) {
489-
typeCheckPatternFailed();
490-
continue;
491-
}
492-
elt.setPattern(pattern);
493-
494-
TypeChecker::diagnoseDuplicateBoundVars(pattern);
495-
496-
// Check the pattern, it allows unspecified types because the pattern can
497-
// provide type information.
498-
auto contextualPattern = ContextualPattern::forRawPattern(pattern, dc);
499-
Type patternType = TypeChecker::typeCheckPattern(contextualPattern);
500-
if (patternType->hasError()) {
501-
typeCheckPatternFailed();
502-
continue;
503-
}
504-
505-
// If the pattern didn't get a type, it's because we ran into some
506-
// unknown types along the way. We'll need to check the initializer.
507-
auto init = elt.getInitializer();
508-
hadError |= TypeChecker::typeCheckBinding(pattern, init, dc, patternType);
509-
elt.setPattern(pattern);
510-
elt.setInitializer(init);
511-
hadAnyFalsable |= pattern->isRefutablePattern();
517+
hadError |=
518+
TypeChecker::typeCheckStmtConditionElement(elt, hadAnyFalsable, dc);
512519
}
513520

514521
// If the binding is not refutable, and there *is* an else, reject it as

lib/Sema/TypeChecker.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,13 @@ bool typesSatisfyConstraint(Type t1, Type t2, bool openArchetypes,
423423
/// of the function, set the result type of the expression to that sugar type.
424424
Expr *substituteInputSugarTypeForResult(ApplyExpr *E);
425425

426+
/// Type check a \c StmtConditionElement.
427+
/// Sets \p isFalsable to \c true if the condition might evaluate to \c false,
428+
/// otherwise leaves \p isFalsable untouched.
429+
/// \returns \c true if there was an error type checking, \c false otherwise.
430+
bool typeCheckStmtConditionElement(StmtConditionElement &elt, bool &isFalsable,
431+
DeclContext *dc);
432+
426433
void typeCheckASTNode(ASTNode &node, DeclContext *DC,
427434
bool LeaveBodyUnchecked = false);
428435

test/IDE/complete_rdar75200217.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -F %S/Inputs/mock-sdk -code-completion-token=COMPLETE | %FileCheck %s
2+
3+
enum Encoding {
4+
case utf8
5+
}
6+
7+
func foo(bytes: ArraySlice<UInt16>, encoding: Encoding) {
8+
fatalError()
9+
}
10+
11+
extension Array {
12+
func bar<R>(r: R) -> ArraySlice<Element> where R : RangeExpression, Int == R.Bound {
13+
fatalError()
14+
}
15+
}
16+
17+
/// The issue, that caused this to fail was that type checking `start` caused
18+
/// the entire `if` condition to be type-checked, thus also type-checking `end`
19+
/// which depends on `start`, thus creating a dependency cycle in the request
20+
/// evaluator.
21+
/// Thus `end` would get assigned an error type, causing `a` to be an error
22+
/// type and thus the completion on `encoding` fails.
23+
/// We need a particular order of type check requests to hit this behaviour,
24+
/// that's why the test case feels over-complicated.
25+
func deserializeName(_ data: Array<UInt16>, flag: Bool) {
26+
if flag, let start = Optional(Array<UInt16>.Index()), let end = Optional(start) {
27+
let range = start..<end
28+
let a = data.bar(r: range)
29+
foo(bytes: a, encoding: .#^COMPLETE^#)
30+
}
31+
}
32+
33+
// CHECK: Begin completions
34+
// CHECK-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: utf8[#Encoding#];
35+
// CHECK: End completions

0 commit comments

Comments
 (0)