Skip to content

Fixes for expression type checking with parse-time name lookup disabled #34088

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: 3 additions & 0 deletions include/swift/AST/ASTTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

SWIFT_TYPEID(ActorIsolation)
SWIFT_TYPEID(AncestryFlags)
SWIFT_TYPEID(BodyInitKind)
SWIFT_TYPEID(BodyInitKindAndExpr)
SWIFT_TYPEID(CtorInitializerKind)
SWIFT_TYPEID(FunctionBuilderBodyPreCheck)
SWIFT_TYPEID(GenericSignature)
Expand All @@ -35,6 +37,7 @@ SWIFT_TYPEID(TypePair)
SWIFT_TYPEID(TypeWitnessAndDecl)
SWIFT_TYPEID(Witness)
SWIFT_TYPEID_NAMED(AbstractFunctionDecl *, AbstractFunctionDecl)
SWIFT_TYPEID_NAMED(ApplyExpr *, ApplyExpr)
SWIFT_TYPEID_NAMED(ClosureExpr *, ClosureExpr)
SWIFT_TYPEID_NAMED(CodeCompletionCallbacksFactory *,
CodeCompletionCallbacksFactory)
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/ASTTypeIDs.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ namespace swift {

class AbstractFunctionDecl;
class ActorIsolation;
class ApplyExpr;
enum class BodyInitKind;
struct BodyInitKindAndExpr;
class BraceStmt;
class ClosureExpr;
class CodeCompletionCallbacksFactory;
Expand Down
71 changes: 34 additions & 37 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -451,14 +451,7 @@ class alignas(1 << DeclAlignInBits) Decl {
/// Whether we have computed the above.
IsTransparentComputed : 1);

SWIFT_INLINE_BITFIELD(ConstructorDecl, AbstractFunctionDecl, 3+1+1,
/// The body initialization kind (+1), or zero if not yet computed.
///
/// This value is cached but is not serialized, because it is a property
/// of the definition of the constructor that is useful only to semantic
/// analysis and SIL generation.
ComputedBodyInitKind : 3,

SWIFT_INLINE_BITFIELD(ConstructorDecl, AbstractFunctionDecl, 1+1,
/// Whether this constructor can fail, by building an Optional type.
Failable : 1,

Expand Down Expand Up @@ -6770,6 +6763,37 @@ enum class CtorInitializerKind {
Factory
};

/// Specifies the kind of initialization call performed within the body
/// of the constructor, e.g., self.init or super.init.
Copy link
Contributor

Choose a reason for hiding this comment

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

Totally optional thought: It could nice to add a sentence like: "Needed to ... when ..."

enum class BodyInitKind {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, good choice to use the enum class vs a straight enum.

/// There are no calls to self.init or super.init.
None,
/// There is a call to self.init, which delegates to another (peer)
/// initializer.
Delegating,
/// There is a call to super.init, which chains to a superclass initializer.
Chained,
/// There are no calls to self.init or super.init explicitly in the body of
/// the constructor, but a 'super.init' call will be implicitly added
/// by semantic analysis.
ImplicitChained
};

struct BodyInitKindAndExpr {
BodyInitKind initKind;
ApplyExpr *initExpr;

BodyInitKindAndExpr() : initKind(BodyInitKind::None), initExpr(nullptr) {}

BodyInitKindAndExpr(BodyInitKind initKind, ApplyExpr *initExpr)
: initKind(initKind), initExpr(initExpr) {}

friend bool operator==(BodyInitKindAndExpr lhs, BodyInitKindAndExpr rhs) {
return (lhs.initKind == rhs.initKind &&
lhs.initExpr == rhs.initExpr);
}
};

/// ConstructorDecl - Declares a constructor for a type. For example:
///
/// \code
Expand Down Expand Up @@ -6818,38 +6842,11 @@ class ConstructorDecl : public AbstractFunctionDecl {

ParamDecl **getImplicitSelfDeclStorage() { return &SelfDecl; }

/// Specifies the kind of initialization call performed within the body
/// of the constructor, e.g., self.init or super.init.
enum class BodyInitKind {
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, saw the insertion of this before the deletion. Didn't realize it wasn't a pure addition.

/// There are no calls to self.init or super.init.
None,
/// There is a call to self.init, which delegates to another (peer)
/// initializer.
Delegating,
/// There is a call to super.init, which chains to a superclass initializer.
Chained,
/// There are no calls to self.init or super.init explicitly in the body of
/// the constructor, but a 'super.init' call will be implicitly added
/// by semantic analysis.
ImplicitChained
};

/// Determine whether the body of this constructor contains any delegating
/// or superclass initializations (\c self.init or \c super.init,
/// respectively) within its body.
///
/// \param diags If non-null, this check will ensure that the constructor
/// body is consistent in its use of delegation vs. chaining and emit any
/// diagnostics through the given diagnostic engine.
///
/// \param init If non-null and there is an explicit \c self.init or
/// \c super.init within the body, will be set to point at that
/// initializer.
BodyInitKind getDelegatingOrChainedInitKind(DiagnosticEngine *diags,
ApplyExpr **init = nullptr) const;
void clearCachedDelegatingOrChainedInitKind() {
Bits.ConstructorDecl.ComputedBodyInitKind = 0;
}
BodyInitKindAndExpr getDelegatingOrChainedInitKind() const;
void clearCachedDelegatingOrChainedInitKind();

/// Whether this constructor is required.
bool isRequired() const {
Expand Down
7 changes: 7 additions & 0 deletions include/swift/AST/Evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ class Evaluator {
cache.insert({AnyRequest(request), std::move(output)});
}

/// Do not introduce new callers of this function.
template<typename Request,
typename std::enable_if<!Request::hasExternalCache>::type* = nullptr>
void clearCachedOutput(const Request &request) {
cache.erase(AnyRequest(request));
}

/// Clear the cache stored within this evaluator.
///
/// Note that this does not clear the caches of requests that use external
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -5793,6 +5793,7 @@ Expr *packSingleArgument(

void simple_display(llvm::raw_ostream &out, const ClosureExpr *CE);
void simple_display(llvm::raw_ostream &out, const DefaultArgumentExpr *expr);
void simple_display(llvm::raw_ostream &out, const Expr *expr);

SourceLoc extractNearestSourceLoc(const DefaultArgumentExpr *expr);

Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/NameLookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,14 @@ class ASTScope {
static void unqualifiedLookup(SourceFile *, SourceLoc,
namelookup::AbstractASTScopeDeclConsumer &);

/// Lookup that only finds local declarations and does not trigger
/// interface type computation.
static void lookupLocalDecls(SourceFile *, DeclName, SourceLoc,
SmallVectorImpl<ValueDecl *> &);

/// Returns the result if there is exactly one, nullptr otherwise.
static ValueDecl *lookupSingleLocalDecl(SourceFile *, DeclName, SourceLoc);

/// Entry point to record the visible statement labels from the given
/// point.
///
Expand Down
23 changes: 23 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ class InitKindRequest :
CtorInitializerKind
evaluate(Evaluator &evaluator, ConstructorDecl *decl) const;

public:
// Caching.
bool isCached() const { return true; }
};

void simple_display(llvm::raw_ostream &out, BodyInitKind initKind);
void simple_display(llvm::raw_ostream &out, BodyInitKindAndExpr initKindAndExpr);

/// Computes the kind of initializer call (self.init or super.init) performed
/// in the body of a \c ConstructorDecl
class BodyInitKindRequest :
public SimpleRequest<BodyInitKindRequest,
BodyInitKindAndExpr(ConstructorDecl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

// Evaluation.
BodyInitKindAndExpr evaluate(Evaluator &evaluator, ConstructorDecl *decl) const;

public:
// Caching.
bool isCached() const { return true; }
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ SWIFT_REQUEST(TypeChecker, InheritsSuperclassInitializersRequest,
bool(ClassDecl *), SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, InitKindRequest,
CtorInitializerKind(ConstructorDecl *), Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, BodyInitKindRequest,
BodyInitKindAndExpr(ConstructorDecl *), Cached,
NoLocationInfo)
SWIFT_REQUEST(TypeChecker, InterfaceTypeRequest,
Type(ValueDecl *), SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, IsAccessorTransparentRequest, bool(AccessorDecl *),
Expand Down
175 changes: 11 additions & 164 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5463,8 +5463,8 @@ bool VarDecl::isSettable(const DeclContext *UseDC,
// If this is a convenience initializer (i.e. one that calls
// self.init), then let properties are never mutable in it. They are
// only mutable in designated initializers.
if (CD->getDelegatingOrChainedInitKind(nullptr) ==
ConstructorDecl::BodyInitKind::Delegating)
auto initKindAndExpr = CD->getDelegatingOrChainedInitKind();
if (initKindAndExpr.initKind == BodyInitKind::Delegating)
return false;

return true;
Expand Down Expand Up @@ -7522,7 +7522,6 @@ ConstructorDecl::ConstructorDecl(DeclName Name, SourceLoc ConstructorLoc,
if (BodyParams)
setParameters(BodyParams);

Bits.ConstructorDecl.ComputedBodyInitKind = 0;
Bits.ConstructorDecl.HasStubImplementation = 0;
Bits.ConstructorDecl.Failable = Failable;

Expand Down Expand Up @@ -7750,169 +7749,17 @@ CtorInitializerKind ConstructorDecl::getInitKind() const {
CtorInitializerKind::Designated);
}

ConstructorDecl::BodyInitKind
ConstructorDecl::getDelegatingOrChainedInitKind(DiagnosticEngine *diags,
ApplyExpr **init) const {
BodyInitKindAndExpr
ConstructorDecl::getDelegatingOrChainedInitKind() const {
return evaluateOrDefault(getASTContext().evaluator,
BodyInitKindRequest{const_cast<ConstructorDecl *>(this)},
BodyInitKindAndExpr());
assert(hasBody() && "Constructor does not have a definition");
}

if (init)
*init = nullptr;

// If we already computed the result, return it.
if (Bits.ConstructorDecl.ComputedBodyInitKind) {
auto Kind = static_cast<BodyInitKind>(
Bits.ConstructorDecl.ComputedBodyInitKind - 1);
assert((Kind == BodyInitKind::None || !init) &&
"can't return cached result with the init expr");
return Kind;
}


struct FindReferenceToInitializer : ASTWalker {
const ConstructorDecl *Decl;
BodyInitKind Kind = BodyInitKind::None;
ApplyExpr *InitExpr = nullptr;
DiagnosticEngine *Diags;

FindReferenceToInitializer(const ConstructorDecl *decl,
DiagnosticEngine *diags)
: Decl(decl), Diags(diags) { }

bool walkToDeclPre(class Decl *D) override {
// Don't walk into further nominal decls.
return !isa<NominalTypeDecl>(D);
}

std::pair<bool, Expr*> walkToExprPre(Expr *E) override {
// Don't walk into closures.
if (isa<ClosureExpr>(E))
return { false, E };

// Look for calls of a constructor on self or super.
auto apply = dyn_cast<ApplyExpr>(E);
if (!apply)
return { true, E };

auto Callee = apply->getSemanticFn();

Expr *arg;

if (isa<OtherConstructorDeclRefExpr>(Callee)) {
arg = apply->getArg();
} else if (auto *CRE = dyn_cast<ConstructorRefCallExpr>(Callee)) {
arg = CRE->getArg();
} else if (auto *dotExpr = dyn_cast<UnresolvedDotExpr>(Callee)) {
if (dotExpr->getName().getBaseName() != DeclBaseName::createConstructor())
return { true, E };

arg = dotExpr->getBase();
} else {
// Not a constructor call.
return { true, E };
}

// Look for a base of 'self' or 'super'.
BodyInitKind myKind;
if (arg->isSuperExpr())
myKind = BodyInitKind::Chained;
else if (arg->isSelfExprOf(Decl, /*sameBase*/true))
myKind = BodyInitKind::Delegating;
else {
// We're constructing something else.
return { true, E };
}

if (Kind == BodyInitKind::None) {
Kind = myKind;

// If we're not emitting diagnostics, we're done.
if (!Diags)
return { false, nullptr };

InitExpr = apply;
return { true, E };
}

assert(Diags && "Failed to abort traversal early");

// If the kind changed, complain.
if (Kind != myKind) {
// The kind changed. Complain.
Diags->diagnose(E->getLoc(), diag::init_delegates_and_chains);
Diags->diagnose(InitExpr->getLoc(), diag::init_delegation_or_chain,
Kind == BodyInitKind::Chained);
}

return { true, E };
}
};

FindReferenceToInitializer finder(this, diags);
getBody()->walk(finder);

// get the kind out of the finder.
auto Kind = finder.Kind;

auto *NTD = getDeclContext()->getSelfNominalTypeDecl();

// Protocol extension and enum initializers are always delegating.
if (Kind == BodyInitKind::None) {
if (isa<ProtocolDecl>(NTD) || isa<EnumDecl>(NTD)) {
Kind = BodyInitKind::Delegating;
}
}

// Struct initializers that cannot see the layout of the struct type are
// always delegating. This occurs if the struct type is not fixed layout,
// and the constructor is either inlinable or defined in another module.
if (Kind == BodyInitKind::None && isa<StructDecl>(NTD)) {
// Note: This is specifically not using isFormallyResilient. We relax this
// rule for structs in non-resilient modules so that they can have inlinable
// constructors, as long as those constructors don't reference private
// declarations.
if (NTD->isResilient() &&
getResilienceExpansion() == ResilienceExpansion::Minimal) {
Kind = BodyInitKind::Delegating;

} else if (isa<ExtensionDecl>(getDeclContext())) {
const ModuleDecl *containingModule = getParentModule();
// Prior to Swift 5, cross-module initializers were permitted to be
// non-delegating. However, if the struct isn't fixed-layout, we have to
// be delegating because, well, we don't know the layout.
// A dynamic replacement is permitted to be non-delegating.
if (NTD->isResilient() ||
(containingModule->getASTContext().isSwiftVersionAtLeast(5) &&
!getAttrs().getAttribute<DynamicReplacementAttr>())) {
if (containingModule != NTD->getParentModule())
Kind = BodyInitKind::Delegating;
}
}
}

// If we didn't find any delegating or chained initializers, check whether
// the initializer was explicitly marked 'convenience'.
if (Kind == BodyInitKind::None && getAttrs().hasAttribute<ConvenienceAttr>())
Kind = BodyInitKind::Delegating;

// If we still don't know, check whether we have a class with a superclass: it
// gets an implicit chained initializer.
if (Kind == BodyInitKind::None) {
if (auto classDecl = getDeclContext()->getSelfClassDecl()) {
if (classDecl->hasSuperclass())
Kind = BodyInitKind::ImplicitChained;
}
}

// Cache the result if it is trustworthy.
if (diags) {
auto *mutableThis = const_cast<ConstructorDecl *>(this);
mutableThis->Bits.ConstructorDecl.ComputedBodyInitKind =
static_cast<unsigned>(Kind) + 1;
if (init)
*init = finder.InitExpr;
}

return Kind;
void ConstructorDecl::clearCachedDelegatingOrChainedInitKind() {
getASTContext().evaluator.clearCachedOutput(
BodyInitKindRequest{const_cast<ConstructorDecl *>(this)});
}

SourceRange DestructorDecl::getSourceRange() const {
Expand Down
Loading