Skip to content

Commit 6942d36

Browse files
authored
Merge pull request #27764 from CodaFi/interface-type-request
Define InterfaceTypeRequest
2 parents 3eabc0d + 72da970 commit 6942d36

File tree

15 files changed

+142
-88
lines changed

15 files changed

+142
-88
lines changed

docs/proposals/DeclarationTypeChecker.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ This document describes some of the problems with our current "declaration" type
1313
Problems with the Current Approach
1414
----------------------------------
1515

16-
The current declaration type checker---in particular, ``validateDecl``, which assigns a type to a given declaration---is the source of a large number of Swift bugs, including crashes on both well-formed and ill-formed code, different behavior depending on the order of declarations within a file or across multiple files, infinite recursion, and broken ASTs. The main issues are:
16+
The current declaration type checker is the source of a large number of Swift bugs, including crashes on both well-formed and ill-formed code, different behavior depending on the order of declarations within a file or across multiple files, infinite recursion, and broken ASTs. The main issues are:
1717

1818
**Conceptual phases are tangled together**: We have a vague notion that there are phases within the compiler, e.g., extension binding occurs before name binding, which occurs before type checking. However, the implementations in the compiler don't respect phases: extension binding goes through type validation, which does both name binding and type checking. Name lookup attempts to do type checking so that it can establish whether one declaration shadows another.
1919

2020
**Unprincipled recursion**: Whenever type checking some particular declaration requires information about another declaration, it recurses to type-check that declaration. There are dozens of these recursion points scattered throughout the compiler, which makes it impossible to reason about the recursion or deal with, e.g., recursion that is too deep for the program stack.
2121

22-
**Ad hoc recursion breaking**: When we do encounter circular dependencies, we have scattered checks for recursion based on a number of separate bits stashed in the AST: ``BeingTypeChecked``, ``EarlyAttrValidation``, ``ValidatingGenericSignature``, etc. Adding these checks is unprincipled: adding a new check in the wrong place tends to break working code (because the dependency is something the compiler should be able to handle), while missing a check permits infinite recursion to continue.
22+
**Ad hoc recursion breaking**: When we do encounter circular dependencies, we have scattered checks for recursion based on a number of separate bits stashed in the AST: ``isComputingRequirementSignature()``, ``isComputingPatternBindingEntry()``, ``isComputingGenericSignature()/hasComputedGenericSignature()``, etc. Adding these checks is unprincipled: adding a new check in the wrong place tends to break working code (because the dependency is something the compiler should be able to handle), while missing a check permits infinite recursion to continue.
2323

2424
**Type checker does too much work**: validating a declaration is all-or-nothing. It includes computing its type, but also checking redeclarations and overrides, as well as numerous other aspects that a user of that declaration might not care about. Aside from the performance impact of checking too much, this can introduce false circularities in type-checking, because the user might only need some very basic information to continue.
2525

@@ -156,7 +156,7 @@ The proposed architecture is significantly different from the current type check
156156

157157
**Dependency graph and priority queue**: Extend the current-phase trait with an operation that enumerates the dependencies that need to be satisfied to bring a given AST node up to a particular phase. Start with ``TypeRepr`` nodes, and use the dependency graph and priority queue to satisfy all dependencies ahead of time, eliminating direct recursion from the type-resolution code path. Build circular-dependency detection within this test-bed.
158158

159-
**Incremental adoption of dependency graph**: Make other AST nodes (``Pattern``, ``VarDecl``, etc.) implement the phase-awareness trait, enumerating dependencies and updating their logic to perform minimal updates. Certain entry points that are used for ad hoc recursion (such as ``validateDecl``) can push/pop dependency graph and priority-queue instances, which leaves the existing ad hoc recursion checking in place but allows isolated subproblems to use the newer mechanisms.
159+
**Incremental adoption of dependency graph**: Make other AST nodes (``Pattern``, ``VarDecl``, etc.) implement the phase-awareness trait, enumerating dependencies and updating their logic to perform minimal updates. Certain entry points that are used for ad hoc recursion can push/pop dependency graph and priority-queue instances, which leaves the existing ad hoc recursion checking in place but allows isolated subproblems to use the newer mechanisms.
160160

161161
**Strengthen accessor assertions**: As ad hoc recursion gets eliminated from the type checker, strengthen assertions on the various AST nodes to make sure the AST node has been brought to the appropriate phase.
162162

include/swift/AST/Decl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,6 +2467,7 @@ class ValueDecl : public Decl {
24672467
friend class IsFinalRequest;
24682468
friend class IsDynamicRequest;
24692469
friend class IsImplicitlyUnwrappedOptionalRequest;
2470+
friend class InterfaceTypeRequest;
24702471
friend class Decl;
24712472
SourceLoc getLocFromSource() const { return NameLoc; }
24722473
protected:

include/swift/AST/LazyResolver.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,6 @@ class LazyResolver {
5353
virtual void resolveWitness(const NormalProtocolConformance *conformance,
5454
ValueDecl *requirement) = 0;
5555

56-
/// Resolve the type and declaration attributes of a value.
57-
///
58-
/// This can be called when the type or signature of a value is needed.
59-
/// It does not perform full type-checking, only checks for basic
60-
/// consistency and provides the value a type.
61-
virtual void resolveDeclSignature(ValueDecl *VD) = 0;
62-
6356
/// Resolve any implicitly-declared constructors within the given nominal.
6457
virtual void resolveImplicitConstructors(NominalTypeDecl *nominal) = 0;
6558

include/swift/AST/TypeCheckRequests.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,6 +1427,27 @@ class NamingPatternRequest
14271427
void cacheResult(NamedPattern *P) const;
14281428
};
14291429

1430+
class InterfaceTypeRequest :
1431+
public SimpleRequest<InterfaceTypeRequest,
1432+
Type (ValueDecl *),
1433+
CacheKind::SeparatelyCached> {
1434+
public:
1435+
using SimpleRequest::SimpleRequest;
1436+
1437+
private:
1438+
friend SimpleRequest;
1439+
1440+
// Evaluation.
1441+
llvm::Expected<Type>
1442+
evaluate(Evaluator &evaluator, ValueDecl *decl) const;
1443+
1444+
public:
1445+
// Separate caching.
1446+
bool isCached() const { return true; }
1447+
Optional<Type> getCachedResult() const;
1448+
void cacheResult(Type value) const;
1449+
};
1450+
14301451
// Allow AnyValue to compare two Type values, even though Type doesn't
14311452
// support ==.
14321453
template<>

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ SWIFT_REQUEST(TypeChecker, InheritedTypeRequest,
6767
SeparatelyCached, HasNearestLocation)
6868
SWIFT_REQUEST(TypeChecker, InitKindRequest,
6969
CtorInitializerKind(ConstructorDecl *), Cached, NoLocationInfo)
70+
SWIFT_REQUEST(TypeChecker, InterfaceTypeRequest,
71+
Type(ValueDecl *), SeparatelyCached, NoLocationInfo)
7072
SWIFT_REQUEST(TypeChecker, IsAccessorTransparentRequest, bool(AccessorDecl *),
7173
SeparatelyCached, NoLocationInfo)
7274
SWIFT_REQUEST(TypeChecker, IsDynamicRequest, bool(ValueDecl *),

lib/AST/Decl.cpp

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2805,30 +2805,23 @@ bool ValueDecl::isRecursiveValidation() const {
28052805
}
28062806

28072807
Type ValueDecl::getInterfaceType() const {
2808-
if (!hasInterfaceType()) {
2809-
// Our clients that don't register the lazy resolver are relying on the
2810-
// fact that they can't pull an interface type out to avoid doing work.
2811-
// This is a necessary evil until we can wean them off.
2812-
if (auto resolver = getASTContext().getLazyResolver()) {
2813-
resolver->resolveDeclSignature(const_cast<ValueDecl *>(this));
2814-
if (!hasInterfaceType())
2815-
return ErrorType::get(getASTContext());
2816-
}
2808+
// Our clients that don't register the lazy resolver are relying on the
2809+
// fact that they can't pull an interface type out to avoid doing work.
2810+
// This is a necessary evil until we can wean them off.
2811+
if (!getASTContext().getLazyResolver()) {
2812+
return TypeAndAccess.getPointer();
28172813
}
2818-
return TypeAndAccess.getPointer();
2814+
2815+
auto ty =
2816+
evaluateOrDefault(getASTContext().evaluator,
2817+
InterfaceTypeRequest{const_cast<ValueDecl *>(this)},
2818+
ErrorType::get(getASTContext()));
2819+
return ty ?: ErrorType::get(getASTContext());
28192820
}
28202821

28212822
void ValueDecl::setInterfaceType(Type type) {
2822-
if (type) {
2823-
assert(!type->hasTypeVariable() && "Type variable in interface type");
2824-
assert(!type->is<InOutType>() && "Interface type must be materializable");
2825-
assert(!type->hasArchetype() && "Archetype in interface type");
2826-
2827-
if (type->hasError())
2828-
setInvalid();
2829-
}
2830-
2831-
TypeAndAccess.setPointer(type);
2823+
getASTContext().evaluator.cacheOutput(InterfaceTypeRequest{this},
2824+
std::move(type));
28322825
}
28332826

28342827
Optional<ObjCSelector> ValueDecl::getObjCRuntimeName(
@@ -4001,7 +3994,7 @@ ClassAncestryFlagsRequest::evaluate(Evaluator &evaluator, ClassDecl *value) cons
40013994
do {
40023995
// If we hit circularity, we will diagnose at some point in typeCheckDecl().
40033996
// However we have to explicitly guard against that here because we get
4004-
// called as part of validateDecl().
3997+
// called as part of the interface type request.
40053998
if (!visited.insert(CD).second)
40063999
break;
40074000

lib/AST/TypeCheckRequests.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,3 +1001,28 @@ void NamingPatternRequest::cacheResult(NamedPattern *value) const {
10011001
auto *VD = std::get<0>(getStorage());
10021002
VD->NamingPattern = value;
10031003
}
1004+
1005+
//----------------------------------------------------------------------------//
1006+
// InterfaceTypeRequest computation.
1007+
//----------------------------------------------------------------------------//
1008+
1009+
Optional<Type> InterfaceTypeRequest::getCachedResult() const {
1010+
auto *decl = std::get<0>(getStorage());
1011+
if (auto Ty = decl->TypeAndAccess.getPointer()) {
1012+
return Ty;
1013+
}
1014+
return None;
1015+
}
1016+
1017+
void InterfaceTypeRequest::cacheResult(Type type) const {
1018+
auto *decl = std::get<0>(getStorage());
1019+
if (type) {
1020+
assert(!type->hasTypeVariable() && "Type variable in interface type");
1021+
assert(!type->is<InOutType>() && "Interface type must be materializable");
1022+
assert(!type->hasArchetype() && "Archetype in interface type");
1023+
1024+
if (type->hasError())
1025+
decl->setInvalid();
1026+
}
1027+
decl->TypeAndAccess.setPointer(type);
1028+
}

lib/Sema/TypeCheckDecl.cpp

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,9 +2287,9 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
22872287
// WARNING: Anything you put in this function will only be run when the
22882288
// VarDecl is fully type-checked within its own file. It will NOT be run
22892289
// when the VarDecl is merely used from another file.
2290-
TC.validateDecl(VD);
22912290

22922291
// Compute these requests in case they emit diagnostics.
2292+
(void) VD->getInterfaceType();
22932293
(void) VD->isGetterMutating();
22942294
(void) VD->isSetterMutating();
22952295
(void) VD->getPropertyWrapperBackingProperty();
@@ -4091,15 +4091,9 @@ static Type validateParameterType(ParamDecl *decl) {
40914091
return TL.getType();
40924092
}
40934093

4094-
void TypeChecker::validateDecl(ValueDecl *D) {
4095-
// Handling validation failure due to re-entrancy is left
4096-
// up to the caller, who must call hasInterfaceType() to
4097-
// check that validateDecl() returned a fully-formed decl.
4098-
if (D->isBeingValidated() || D->hasInterfaceType())
4099-
return;
4100-
4101-
PrettyStackTraceDecl StackTrace("validating", D);
4102-
FrontendStatsTracer StatsTracer(Context.Stats, "validate-decl", D);
4094+
llvm::Expected<Type>
4095+
InterfaceTypeRequest::evaluate(Evaluator &eval, ValueDecl *D) const {
4096+
auto &Context = D->getASTContext();
41034097

41044098
TypeChecker::checkForForbiddenPrefix(Context, D->getBaseName());
41054099

@@ -4123,13 +4117,12 @@ void TypeChecker::validateDecl(ValueDecl *D) {
41234117
case DeclKind::OpaqueType:
41244118
case DeclKind::GenericTypeParam:
41254119
llvm_unreachable("should not get here");
4126-
return;
4120+
return Type();
41274121

41284122
case DeclKind::AssociatedType: {
41294123
auto assocType = cast<AssociatedTypeDecl>(D);
41304124
auto interfaceTy = assocType->getDeclaredInterfaceType();
4131-
assocType->setInterfaceType(MetatypeType::get(interfaceTy, Context));
4132-
break;
4125+
return MetatypeType::get(interfaceTy, Context);
41334126
}
41344127

41354128
case DeclKind::TypeAlias: {
@@ -4146,8 +4139,7 @@ void TypeChecker::validateDecl(ValueDecl *D) {
41464139
parent = parentDC->getSelfInterfaceType();
41474140
auto sugaredType = TypeAliasType::get(typeAlias, parent, subs,
41484141
typeAlias->getUnderlyingType());
4149-
typeAlias->setInterfaceType(MetatypeType::get(sugaredType, Context));
4150-
break;
4142+
return MetatypeType::get(sugaredType, Context);
41514143
}
41524144

41534145
case DeclKind::Enum:
@@ -4156,8 +4148,7 @@ void TypeChecker::validateDecl(ValueDecl *D) {
41564148
case DeclKind::Protocol: {
41574149
auto nominal = cast<NominalTypeDecl>(D);
41584150
Type declaredInterfaceTy = nominal->getDeclaredInterfaceType();
4159-
nominal->setInterfaceType(MetatypeType::get(declaredInterfaceTy, Context));
4160-
break;
4151+
return MetatypeType::get(declaredInterfaceTy, Context);
41614152
}
41624153

41634154
case DeclKind::Param: {
@@ -4167,40 +4158,33 @@ void TypeChecker::validateDecl(ValueDecl *D) {
41674158
auto selfParam = computeSelfParam(AFD,
41684159
/*isInitializingCtor*/true,
41694160
/*wantDynamicSelf*/true);
4170-
PD->setInterfaceType(selfParam.getPlainType());
4171-
break;
4161+
return selfParam.getPlainType();
41724162
}
41734163

41744164
if (auto *accessor = dyn_cast<AccessorDecl>(PD->getDeclContext())) {
41754165
auto *storage = accessor->getStorage();
41764166
auto *originalParam = getOriginalParamFromAccessor(
41774167
storage, accessor, PD);
41784168
if (originalParam == nullptr) {
4179-
auto type = storage->getValueInterfaceType();
4180-
PD->setInterfaceType(type);
4181-
break;
4169+
return storage->getValueInterfaceType();
41824170
}
41834171

41844172
if (originalParam != PD) {
4185-
PD->setInterfaceType(originalParam->getInterfaceType());
4186-
break;
4173+
return originalParam->getInterfaceType();
41874174
}
41884175
}
41894176

41904177
if (!PD->getTypeRepr())
4191-
return;
4178+
return Type();
41924179

4193-
auto ty = validateParameterType(PD);
4194-
PD->setInterfaceType(ty);
4195-
break;
4180+
return validateParameterType(PD);
41964181
}
41974182

41984183
case DeclKind::Var: {
41994184
auto *VD = cast<VarDecl>(D);
42004185
auto *namingPattern = VD->getNamingPattern();
42014186
if (!namingPattern) {
4202-
VD->setInterfaceType(ErrorType::get(Context));
4203-
break;
4187+
return ErrorType::get(Context);
42044188
}
42054189

42064190
Type interfaceType = namingPattern->getType();
@@ -4213,9 +4197,8 @@ void TypeChecker::validateDecl(ValueDecl *D) {
42134197
interfaceType =
42144198
TypeChecker::checkReferenceOwnershipAttr(VD, interfaceType, attr);
42154199
}
4216-
VD->setInterfaceType(interfaceType);
42174200

4218-
break;
4201+
return interfaceType;
42194202
}
42204203

42214204
case DeclKind::Func:
@@ -4224,8 +4207,54 @@ void TypeChecker::validateDecl(ValueDecl *D) {
42244207
case DeclKind::Destructor: {
42254208
auto *AFD = cast<AbstractFunctionDecl>(D);
42264209
DeclValidationRAII IBV(AFD);
4227-
AFD->computeType();
4228-
break;
4210+
4211+
auto sig = AFD->getGenericSignature();
4212+
bool hasSelf = AFD->hasImplicitSelfDecl();
4213+
4214+
AnyFunctionType::ExtInfo info;
4215+
4216+
// Result
4217+
Type resultTy;
4218+
if (auto fn = dyn_cast<FuncDecl>(D)) {
4219+
resultTy = fn->getResultInterfaceType();
4220+
} else if (auto ctor = dyn_cast<ConstructorDecl>(D)) {
4221+
resultTy = ctor->getResultInterfaceType();
4222+
} else {
4223+
assert(isa<DestructorDecl>(D));
4224+
resultTy = TupleType::getEmpty(AFD->getASTContext());
4225+
}
4226+
4227+
// (Args...) -> Result
4228+
Type funcTy;
4229+
4230+
{
4231+
SmallVector<AnyFunctionType::Param, 4> argTy;
4232+
AFD->getParameters()->getParams(argTy);
4233+
4234+
// 'throws' only applies to the innermost function.
4235+
info = info.withThrows(AFD->hasThrows());
4236+
// Defer bodies must not escape.
4237+
if (auto fd = dyn_cast<FuncDecl>(D))
4238+
info = info.withNoEscape(fd->isDeferBody());
4239+
4240+
if (sig && !hasSelf) {
4241+
funcTy = GenericFunctionType::get(sig, argTy, resultTy, info);
4242+
} else {
4243+
funcTy = FunctionType::get(argTy, resultTy, info);
4244+
}
4245+
}
4246+
4247+
// (Self) -> (Args...) -> Result
4248+
if (hasSelf) {
4249+
// Substitute in our own 'self' parameter.
4250+
auto selfParam = computeSelfParam(AFD);
4251+
if (sig)
4252+
funcTy = GenericFunctionType::get(sig, {selfParam}, funcTy);
4253+
else
4254+
funcTy = FunctionType::get({selfParam}, funcTy);
4255+
}
4256+
4257+
return funcTy;
42294258
}
42304259

42314260
case DeclKind::Subscript: {
@@ -4243,9 +4272,7 @@ void TypeChecker::validateDecl(ValueDecl *D) {
42434272
else
42444273
funcTy = FunctionType::get(argTy, elementTy);
42454274

4246-
// Record the interface type.
4247-
SD->setInterfaceType(funcTy);
4248-
break;
4275+
return funcTy;
42494276
}
42504277

42514278
case DeclKind::EnumElement: {
@@ -4272,13 +4299,9 @@ void TypeChecker::validateDecl(ValueDecl *D) {
42724299
else
42734300
resultTy = FunctionType::get({selfTy}, resultTy);
42744301

4275-
// Record the interface type.
4276-
EED->setInterfaceType(resultTy);
4277-
break;
4302+
return resultTy;
42784303
}
42794304
}
4280-
4281-
assert(D->hasInterfaceType());
42824305
}
42834306

42844307
llvm::Expected<NamedPattern *>

lib/Sema/TypeCheckStorage.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ void swift::setBoundVarsTypeError(Pattern *pattern, ASTContext &ctx) {
3939
pattern->forEachVariable([&](VarDecl *var) {
4040
// Don't change the type of a variable that we've been able to
4141
// compute a type for.
42-
if (var->hasInterfaceType() && !var->getType()->hasError())
42+
if (var->hasInterfaceType())
4343
return;
4444

4545
var->setInterfaceType(ErrorType::get(var->getASTContext()));

0 commit comments

Comments
 (0)