Skip to content

Commit 4bc4030

Browse files
author
David Ungar
committed
Catch failures earlier caused by premature requests for extended nominal
1 parent 8ca0f0d commit 4bc4030

File tree

8 files changed

+66
-12
lines changed

8 files changed

+66
-12
lines changed

include/swift/AST/Decl.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,11 @@ class ExtensionDecl final : public GenericContext, public Decl,
16771677
TypeRepr *ExtendedTypeRepr;
16781678

16791679
/// The nominal type being extended.
1680-
NominalTypeDecl *ExtendedNominal = nullptr;
1680+
///
1681+
/// The bit indicates whether binding has been attempted. The pointer can be
1682+
/// null if either no binding was attempted or if binding could not find the
1683+
/// extended nominal.
1684+
llvm::PointerIntPair<NominalTypeDecl *, 1, bool> ExtendedNominal;
16811685

16821686
MutableArrayRef<TypeLoc> Inherited;
16831687

@@ -1736,6 +1740,12 @@ class ExtensionDecl final : public GenericContext, public Decl,
17361740
SourceRange getBraces() const { return Braces; }
17371741
void setBraces(SourceRange braces) { Braces = braces; }
17381742

1743+
bool hasBeenBound() const { return ExtendedNominal.getInt(); }
1744+
1745+
void setExtendedNominal(NominalTypeDecl *n) {
1746+
ExtendedNominal.setPointerAndInt(n, true);
1747+
}
1748+
17391749
/// Retrieve the type being extended.
17401750
///
17411751
/// Only use this entry point when the complete type, as spelled in the source,
@@ -1744,8 +1754,21 @@ class ExtensionDecl final : public GenericContext, public Decl,
17441754
Type getExtendedType() const;
17451755

17461756
/// Retrieve the nominal type declaration that is being extended.
1757+
/// Will trip an assertion if the declaration has not already been computed.
1758+
/// In order to fail fast when type checking work is attempted
1759+
/// before extension binding has taken place.
1760+
17471761
NominalTypeDecl *getExtendedNominal() const;
17481762

1763+
/// Compute the nominal type declaration that is being extended.
1764+
NominalTypeDecl *computeExtendedNominal() const;
1765+
1766+
/// \c hasBeenBound means nothing if this extension can never been bound
1767+
/// because it is not at the top level.
1768+
bool canNeverBeBound() const;
1769+
1770+
bool hasValidParent() const;
1771+
17491772
/// Determine whether this extension has already been bound to a nominal
17501773
/// type declaration.
17511774
bool alreadyBoundToNominal() const { return NextExtension.getInt(); }

include/swift/AST/DeclContext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,10 @@ class alignas(1 << DeclContextAlignInBits) DeclContext {
461461
/// AnyObject dynamic lookup.
462462
bool mayContainMembersAccessedByDynamicLookup() const;
463463

464+
/// Extensions are only allowed at the level in a file
465+
/// FIXME: do this for Protocols, too someday
466+
bool canBeParentOfExtension() const;
467+
464468
/// Returns true if lookups within this context could affect downstream files.
465469
///
466470
/// \param functionsAreNonCascading If true, functions are considered non-

lib/AST/Decl.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,9 +1076,25 @@ ExtensionDecl::takeConformanceLoaderSlow() {
10761076
}
10771077

10781078
NominalTypeDecl *ExtensionDecl::getExtendedNominal() const {
1079+
assert((hasBeenBound() || canNeverBeBound()) &&
1080+
"Extension must have already been bound (by bindExtensions)");
1081+
return ExtendedNominal.getPointer();
1082+
}
1083+
1084+
NominalTypeDecl *ExtensionDecl::computeExtendedNominal() const {
10791085
ASTContext &ctx = getASTContext();
1080-
return evaluateOrDefault(ctx.evaluator,
1081-
ExtendedNominalRequest{const_cast<ExtensionDecl *>(this)}, nullptr);
1086+
return evaluateOrDefault(
1087+
ctx.evaluator, ExtendedNominalRequest{const_cast<ExtensionDecl *>(this)},
1088+
nullptr);
1089+
}
1090+
1091+
bool ExtensionDecl::canNeverBeBound() const {
1092+
// \c bindExtensions() only looks at valid parents for extensions.
1093+
return !hasValidParent();
1094+
}
1095+
1096+
bool ExtensionDecl::hasValidParent() const {
1097+
return getDeclContext()->canBeParentOfExtension();
10821098
}
10831099

10841100
bool ExtensionDecl::isConstrainedExtension() const {

lib/AST/DeclContext.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,10 @@ bool DeclContext::mayContainMembersAccessedByDynamicLookup() const {
478478
return false;
479479
}
480480

481+
bool DeclContext::canBeParentOfExtension() const {
482+
return isa<SourceFile>(this);
483+
}
484+
481485
bool DeclContext::walkContext(ASTWalker &Walker) {
482486
switch (getContextKind()) {
483487
case DeclContextKind::Module:

lib/AST/NameLookupRequests.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,19 @@ Optional<NominalTypeDecl *> ExtendedNominalRequest::getCachedResult() const {
7474
// Note: if we fail to compute any nominal declaration, it's considered
7575
// a cache miss. This allows us to recompute the extended nominal types
7676
// during extension binding.
77+
// This recomputation is also what allows you to extend types defined inside
78+
// other extensions, regardless of source file order. See \c bindExtensions(),
79+
// which uses a worklist algorithm that attempts to bind everything until
80+
// fixed point.
7781
auto ext = std::get<0>(getStorage());
78-
if (ext->ExtendedNominal)
79-
return ext->ExtendedNominal;
80-
81-
return None;
82+
if (!ext->hasBeenBound() || !ext->getExtendedNominal())
83+
return None;
84+
return ext->getExtendedNominal();
8285
}
8386

8487
void ExtendedNominalRequest::cacheResult(NominalTypeDecl *value) const {
8588
auto ext = std::get<0>(getStorage());
86-
if (value)
87-
ext->ExtendedNominal = value;
89+
ext->setExtendedNominal(value);
8890
}
8991

9092
//----------------------------------------------------------------------------//

lib/IDE/SyntaxModel.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,9 @@ bool ModelASTWalker::walkToDeclPre(Decl *D) {
792792
pushStructureNode(SN, NTD);
793793

794794
} else if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
795+
// Normally bindExtension() would take care of computing the extended
796+
// nominal. It must be done before asking for generic parameters.
797+
ED->computeExtendedNominal();
795798
SyntaxStructureNode SN;
796799
setDecl(SN, D);
797800
SN.Kind = SyntaxStructureKind::Extension;

lib/Sema/TypeCheckDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3058,7 +3058,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
30583058
// Produce any diagnostics for the extended type.
30593059
auto extType = ED->getExtendedType();
30603060

3061-
auto nominal = ED->getExtendedNominal();
3061+
auto nominal = ED->computeExtendedNominal();
30623062
if (nominal == nullptr) {
30633063
const bool wasAlreadyInvalid = ED->isInvalid();
30643064
ED->setInvalid();

lib/Sema/TypeChecker.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ static void bindExtensions(SourceFile &SF) {
229229
// Utility function to try and resolve the extended type without diagnosing.
230230
// If we succeed, we go ahead and bind the extension. Otherwise, return false.
231231
auto tryBindExtension = [&](ExtensionDecl *ext) -> bool {
232-
if (auto nominal = ext->getExtendedNominal()) {
232+
assert(!ext->canNeverBeBound() &&
233+
"Only extensions that can ever be bound get here.");
234+
if (auto nominal = ext->computeExtendedNominal()) {
233235
bindExtensionToNominal(ext, nominal);
234236
return true;
235237
}
@@ -615,7 +617,7 @@ void swift::typeCheckCompletionDecl(Decl *D) {
615617
DiagnosticSuppression suppression(Ctx.Diags);
616618
(void)createTypeChecker(Ctx);
617619
if (auto ext = dyn_cast<ExtensionDecl>(D)) {
618-
if (auto *nominal = ext->getExtendedNominal()) {
620+
if (auto *nominal = ext->computeExtendedNominal()) {
619621
// FIXME(InterfaceTypeRequest): Remove this.
620622
(void)nominal->getInterfaceType();
621623
}

0 commit comments

Comments
 (0)