Skip to content

Commit a83ea3c

Browse files
authored
Merge pull request #84745 from hamishknight/fishmonger
[Evaluator] Enforce consistent results for cyclic requests
2 parents b9fd3e4 + 2ce7f1c commit a83ea3c

25 files changed

+187
-151
lines changed

include/swift/AST/Evaluator.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ class Evaluator {
164164
/// is treated as a stack and is used to detect cycles.
165165
llvm::SetVector<ActiveRequest> activeRequests;
166166

167+
/// A set of active requests that have been diagnosed for a cycle.
168+
llvm::DenseSet<ActiveRequest> diagnosedActiveCycles;
169+
167170
/// A cache that stores the results of requests.
168171
evaluator::RequestCache cache;
169172

@@ -344,7 +347,17 @@ class Evaluator {
344347

345348
recorder.beginRequest<Request>();
346349

347-
auto result = getRequestFunction<Request>()(request, *this);
350+
auto result = [&]() -> typename Request::OutputType {
351+
auto reqResult = getRequestFunction<Request>()(request, *this);
352+
353+
// If we diagnosed a cycle for this request, we want to only use the
354+
// default value to ensure we return a consistent result.
355+
if (!diagnosedActiveCycles.empty() &&
356+
diagnosedActiveCycles.erase(activeReq)) {
357+
return defaultValueFn();
358+
}
359+
return reqResult;
360+
}();
348361

349362
recorder.endRequest<Request>(request);
350363

include/swift/AST/GenericSignature.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ class GenericSignature {
120120
ArrayRef<Requirement> requirements,
121121
bool isKnownCanonical = false);
122122

123+
/// Create a new placeholder generic signature from a set of generic
124+
/// parameters. This is necessary for recovery in invalid cases.
125+
static GenericSignature forInvalid(ArrayRef<GenericTypeParamType *> params);
126+
123127
/// Produce a new generic signature which drops all of the marker
124128
/// protocol conformance requirements associated with this one.
125129
GenericSignature withoutMarkerProtocols() const;

lib/AST/ASTContext.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3618,15 +3618,11 @@ static Type replacingTypeVariablesAndPlaceholders(Type ty) {
36183618
Type ErrorType::get(Type originalType) {
36193619
// The original type is only used for printing/debugging, and we don't support
36203620
// solver-allocated ErrorTypes. As such, fold any type variables and
3621-
// placeholders into bare ErrorTypes, which print as placeholders.
3622-
//
3623-
// FIXME: If the originalType is itself an ErrorType we ought to be flattening
3624-
// it, but that's currently load-bearing as it avoids crashing for recursive
3625-
// generic signatures such as in `0120-rdar34184392.swift`. To fix this we
3626-
// ought to change the evaluator to ensure the outer step of a request cycle
3627-
// returns the same default value as the inner step such that we don't end up
3628-
// with conflicting generic signatures on encountering a cycle.
3621+
// placeholders into ErrorTypes. If we have a top-level one, we can return
3622+
// that directly.
36293623
originalType = replacingTypeVariablesAndPlaceholders(originalType);
3624+
if (isa<ErrorType>(originalType.getPointer()))
3625+
return originalType;
36303626

36313627
ASSERT(originalType);
36323628
ASSERT(!originalType->getRecursiveProperties().isSolverAllocated() &&
@@ -6009,6 +6005,25 @@ GenericSignature::get(ArrayRef<GenericTypeParamType *> params,
60096005
return newSig;
60106006
}
60116007

6008+
GenericSignature
6009+
GenericSignature::forInvalid(ArrayRef<GenericTypeParamType *> params) {
6010+
ASSERT(!params.empty());
6011+
auto &ctx = params.front()->getASTContext();
6012+
6013+
// Add same type requirements that make each of the generic parameters
6014+
// concrete error types. This helps avoid downstream diagnostics and is
6015+
// handled the same as if the user wrote e.g `<T where T == Undefined>`.
6016+
SmallVector<Requirement, 2> requirements;
6017+
for (auto *param : params) {
6018+
if (param->isValue())
6019+
continue;
6020+
6021+
requirements.emplace_back(RequirementKind::SameType, param,
6022+
ErrorType::get(ctx));
6023+
}
6024+
return GenericSignature::get(params, requirements);
6025+
}
6026+
60126027
GenericEnvironment *GenericEnvironment::forPrimary(GenericSignature signature) {
60136028
auto &ctx = signature->getASTContext();
60146029

lib/AST/Decl.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,8 +1643,7 @@ bool GenericContext::isComputingGenericSignature() const {
16431643

16441644
/// If we hit a cycle while building the generic signature, we can't return
16451645
/// nullptr, since this breaks invariants elsewhere. Instead, build a dummy
1646-
/// signature where everything is Copyable and Escapable, to avoid spurious
1647-
/// downstream diagnostics concerning move-only types.
1646+
/// invalid signature to avoid spurious downstream diagnostics.
16481647
static GenericSignature getPlaceholderGenericSignature(
16491648
ASTContext &ctx, const DeclContext *DC) {
16501649
SmallVector<GenericParamList *, 2> gpLists;
@@ -1660,22 +1659,13 @@ static GenericSignature getPlaceholderGenericSignature(
16601659
gpLists[i]->setDepth(i);
16611660

16621661
SmallVector<GenericTypeParamType *, 2> genericParams;
1663-
SmallVector<Requirement, 2> requirements;
1664-
16651662
for (auto *gpList : gpLists) {
16661663
for (auto *genericParam : *gpList) {
16671664
auto type = genericParam->getDeclaredInterfaceType();
16681665
genericParams.push_back(type->castTo<GenericTypeParamType>());
1669-
1670-
for (auto ip : InvertibleProtocolSet::allKnown()) {
1671-
auto proto = ctx.getProtocol(getKnownProtocolKind(ip));
1672-
requirements.emplace_back(RequirementKind::Conformance, type,
1673-
proto->getDeclaredInterfaceType());
1674-
}
16751666
}
16761667
}
1677-
1678-
return GenericSignature::get(genericParams, requirements);
1668+
return GenericSignature::forInvalid(genericParams);
16791669
}
16801670

16811671
GenericSignature GenericContext::getGenericSignature() const {
@@ -2700,10 +2690,16 @@ bool PatternBindingDecl::hasStorage() const {
27002690

27012691
const PatternBindingEntry *
27022692
PatternBindingDecl::getCheckedPatternBindingEntry(unsigned i) const {
2703-
return evaluateOrDefault(
2693+
auto result = evaluateOrDefault(
27042694
getASTContext().evaluator,
27052695
PatternBindingEntryRequest{const_cast<PatternBindingDecl *>(this), i},
27062696
nullptr);
2697+
2698+
// If we ran into a cycle, we can still return the entry.
2699+
if (!result)
2700+
return &getPatternList()[i];
2701+
2702+
return result;
27072703
}
27082704

27092705
void PatternBindingDecl::setPattern(unsigned i, Pattern *P,
@@ -7392,8 +7388,7 @@ bool ProtocolDecl::existentialConformsToSelf() const {
73927388
}
73937389

73947390
bool ProtocolDecl::hasSelfOrAssociatedTypeRequirements() const {
7395-
// Because we will have considered all the protocols in a cyclic hierarchy by
7396-
// the time the cycle is hit.
7391+
// Be conservative and avoid diagnosing if we hit a cycle.
73977392
const bool resultForCycle = false;
73987393

73997394
return evaluateOrDefault(getASTContext().evaluator,

lib/AST/Evaluator.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,12 @@ void Evaluator::diagnoseCycle(const ActiveRequest &request) {
155155
request.diagnoseCycle(cycleDiags.getDiags());
156156

157157
for (const auto &step : llvm::reverse(activeRequests)) {
158-
if (step == request)
158+
if (step == request) {
159+
// Note that we diagnosed a cycle for the outermost step to ensure it
160+
// also returns a cyclic result.
161+
diagnosedActiveCycles.insert(step);
159162
return;
163+
}
160164
step.noteCycleStep(cycleDiags.getDiags());
161165
}
162166

lib/AST/RequirementMachine/RequirementMachineRequests.cpp

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -750,26 +750,6 @@ AbstractGenericSignatureRequest::evaluate(
750750
}
751751
}
752752

753-
/// If completion fails, build a dummy generic signature where everything is
754-
/// Copyable and Escapable, to avoid spurious downstream diagnostics
755-
/// concerning move-only types.
756-
static GenericSignature getPlaceholderGenericSignature(
757-
ASTContext &ctx, ArrayRef<GenericTypeParamType *> genericParams) {
758-
SmallVector<Requirement, 2> requirements;
759-
for (auto param : genericParams) {
760-
if (param->isValue())
761-
continue;
762-
763-
for (auto ip : InvertibleProtocolSet::allKnown()) {
764-
auto proto = ctx.getProtocol(getKnownProtocolKind(ip));
765-
requirements.emplace_back(RequirementKind::Conformance, param,
766-
proto->getDeclaredInterfaceType());
767-
}
768-
}
769-
770-
return GenericSignature::get(genericParams, requirements);
771-
}
772-
773753
GenericSignatureWithError
774754
InferredGenericSignatureRequest::evaluate(
775755
Evaluator &evaluator,
@@ -996,7 +976,7 @@ InferredGenericSignatureRequest::evaluate(
996976
diag::requirement_machine_completion_rule,
997977
rule);
998978

999-
auto result = getPlaceholderGenericSignature(ctx, genericParams);
979+
auto result = GenericSignature::forInvalid(genericParams);
1000980

1001981
if (rewriteCtx.getDebugOptions().contains(DebugFlags::Timers)) {
1002982
rewriteCtx.endTimer("InferredGenericSignatureRequest");

lib/Sema/ConstraintSystem.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,9 +1917,6 @@ static Type replacePlaceholderType(PlaceholderType *placeholder,
19171917

19181918
return Type(gp);
19191919
});
1920-
if (isa<TypeVariableType>(replacement.getPointer()))
1921-
return ErrorType::get(ctx);
1922-
19231920
// For completion, we want to produce an archetype instead of an ErrorType
19241921
// for a top-level generic parameter.
19251922
// FIXME: This is pretty weird, we're producing a contextual type outside of
@@ -1930,8 +1927,8 @@ static Type replacePlaceholderType(PlaceholderType *placeholder,
19301927
return GP->getDecl()->getInnermostDeclContext()->mapTypeIntoContext(GP);
19311928
}
19321929
// Return an ErrorType with the replacement as the original type. Note that
1933-
// if we failed to replace a type variable with a generic parameter in a
1934-
// dependent member, `ErrorType::get` will fold it away.
1930+
// if we failed to replace a type variable with a generic parameter,
1931+
// `ErrorType::get` will fold it away.
19351932
return ErrorType::get(replacement);
19361933
}
19371934

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,8 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD,
893893
return hadError;
894894
}
895895

896-
bool TypeChecker::typeCheckForEachPreamble(DeclContext *dc, ForEachStmt *stmt) {
896+
bool TypeChecker::typeCheckForEachPreamble(DeclContext *dc, ForEachStmt *stmt,
897+
bool skipWhere) {
897898
auto &Context = dc->getASTContext();
898899
FrontendStatsTracer statsTracer(Context.Stats, "typecheck-for-each", stmt);
899900
PrettyStackTraceStmt stackTrace(Context, "type-checking-for-each", stmt);
@@ -902,18 +903,20 @@ bool TypeChecker::typeCheckForEachPreamble(DeclContext *dc, ForEachStmt *stmt) {
902903
if (!typeCheckTarget(target))
903904
return true;
904905

905-
if (auto *where = stmt->getWhere()) {
906-
auto boolType = dc->getASTContext().getBoolType();
907-
if (!boolType)
908-
return true;
906+
if (!skipWhere) {
907+
if (auto *where = stmt->getWhere()) {
908+
auto boolType = dc->getASTContext().getBoolType();
909+
if (!boolType)
910+
return true;
909911

910-
SyntacticElementTarget whereClause(where, dc, {boolType, CTP_Condition},
911-
/*isDiscarded=*/false);
912-
auto result = typeCheckTarget(whereClause);
913-
if (!result)
914-
return true;
912+
SyntacticElementTarget whereClause(where, dc, {boolType, CTP_Condition},
913+
/*isDiscarded=*/false);
914+
auto result = typeCheckTarget(whereClause);
915+
if (!result)
916+
return true;
915917

916-
stmt->setWhere(result->getAsExpr());
918+
stmt->setWhere(result->getAsExpr());
919+
}
917920
}
918921

919922
// Check to see if the sequence expr is throwing (in async context),

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,17 +1156,22 @@ swift::matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache,
11561156
getOrCreateRequirementEnvironment(reqEnvCache, dc, reqSig, proto,
11571157
covariantSelf, conformance);
11581158

1159+
auto reqSubMap = reqEnvironment.getRequirementToWitnessThunkSubs();
1160+
1161+
Type selfTy = proto->getSelfInterfaceType().subst(reqSubMap);
1162+
if (selfTy->hasError()) {
1163+
return RequirementMatch(witness, MatchKind::WitnessInvalid,
1164+
witnessType, reqEnvironment,
1165+
/*optionalAdjustments*/ {});
1166+
}
1167+
11591168
// Set up the constraint system for matching.
11601169
auto setup =
11611170
[&]() -> std::tuple<std::optional<RequirementMatch>, Type, Type, Type, Type> {
11621171
// Construct a constraint system to use to solve the equality between
11631172
// the required type and the witness type.
11641173
cs.emplace(dc, ConstraintSystemFlags::AllowFixes);
11651174

1166-
auto reqSubMap = reqEnvironment.getRequirementToWitnessThunkSubs();
1167-
1168-
Type selfTy = proto->getSelfInterfaceType().subst(reqSubMap);
1169-
11701175
// Open up the type of the requirement.
11711176
reqLocator =
11721177
cs->getConstraintLocator(req, ConstraintLocator::ProtocolRequirement);

lib/Sema/TypeCheckStmt.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1481,7 +1481,15 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
14811481
}
14821482

14831483
Stmt *visitForEachStmt(ForEachStmt *S) {
1484-
TypeChecker::typeCheckForEachPreamble(DC, S);
1484+
// If we're performing IDE inspection, we also want to skip the where
1485+
// clause if we're leaving the body unchecked.
1486+
// FIXME: This is a hack to avoid cycles through NamingPatternRequest when
1487+
// doing lazy type-checking, we ought to fix the request to be granular in
1488+
// the type-checking work it kicks.
1489+
bool skipWhere = LeaveBraceStmtBodyUnchecked &&
1490+
Ctx.SourceMgr.hasIDEInspectionTargetBuffer();
1491+
1492+
TypeChecker::typeCheckForEachPreamble(DC, S, skipWhere);
14851493

14861494
// Type-check the body of the loop.
14871495
auto sourceFile = DC->getParentSourceFile();

0 commit comments

Comments
 (0)