AST/SILGen: Requestify var initializer expression typechecking.

Allow initializer expressions to be emitted during SILGen when
`-experimental-lazy-typecheck` is specified by introducing a new request that
fully typechecks the init expressions of pattern binding declarations
on-demand.

There are still a few rough edges, like missing support for wrapped properties
and incomplete handling of subsumed initializers. Fixing these issues is not an
immediate priority because in the short term `-experimental-lazy-typecheck`
will always be accompanied by `-enable-library-evolution` and
`-experimental-skip-non-exportable-decls`. This means that only the
initializers of properties on `@frozen` types will need to be emitted and
property wrappers are not yet fully supported on properties belonging to
`@frozen` types.

Resolves rdar://117448868
This commit is contained in:
Allan Shortlidge
2023-11-13 21:39:08 -08:00
parent 89bda715ce
commit 111eea7f5d
13 changed files with 189 additions and 22 deletions

View File

@@ -2276,6 +2276,10 @@ public:
getMutablePatternList()[i].setOriginalInit(E); getMutablePatternList()[i].setOriginalInit(E);
} }
/// Returns a fully typechecked executable init expression for the pattern at
/// the given index.
Expr *getCheckedExecutableInit(unsigned i) const;
Pattern *getPattern(unsigned i) const { Pattern *getPattern(unsigned i) const {
return getPatternList()[i].getPattern(); return getPatternList()[i].getPattern();
} }

View File

@@ -68,10 +68,11 @@ protected:
// clang-format off // clang-format off
union { uint64_t OpaqueBits; union { uint64_t OpaqueBits;
SWIFT_INLINE_BITFIELD_BASE(Pattern, bitmax(NumPatternKindBits,8)+1+1, SWIFT_INLINE_BITFIELD_BASE(Pattern, bitmax(NumPatternKindBits,8)+1+1+1,
Kind : bitmax(NumPatternKindBits,8), Kind : bitmax(NumPatternKindBits,8),
isImplicit : 1, isImplicit : 1,
hasInterfaceType : 1 hasInterfaceType : 1,
executableInitChecked : 1
); );
SWIFT_INLINE_BITFIELD_FULL(TuplePattern, Pattern, 32, SWIFT_INLINE_BITFIELD_FULL(TuplePattern, Pattern, 32,
@@ -104,6 +105,7 @@ protected:
Bits.Pattern.Kind = unsigned(kind); Bits.Pattern.Kind = unsigned(kind);
Bits.Pattern.isImplicit = false; Bits.Pattern.isImplicit = false;
Bits.Pattern.hasInterfaceType = false; Bits.Pattern.hasInterfaceType = false;
Bits.Pattern.executableInitChecked = false;
} }
private: private:
@@ -136,6 +138,11 @@ public:
bool isImplicit() const { return Bits.Pattern.isImplicit; } bool isImplicit() const { return Bits.Pattern.isImplicit; }
void setImplicit() { Bits.Pattern.isImplicit = true; } void setImplicit() { Bits.Pattern.isImplicit = true; }
bool executableInitChecked() const {
return Bits.Pattern.executableInitChecked;
}
void setExecutableInitChecked() { Bits.Pattern.executableInitChecked = true; }
/// Find the smallest subpattern which obeys the property that matching it is /// Find the smallest subpattern which obeys the property that matching it is
/// equivalent to matching this pattern. /// equivalent to matching this pattern.
/// ///

View File

@@ -2347,6 +2347,27 @@ public:
void cacheResult(const PatternBindingEntry *value) const; void cacheResult(const PatternBindingEntry *value) const;
}; };
class PatternBindingCheckedExecutableInitRequest
: public SimpleRequest<PatternBindingCheckedExecutableInitRequest,
Expr *(PatternBindingDecl *, unsigned),
RequestFlags::SeparatelyCached> {
public:
using SimpleRequest::SimpleRequest;
private:
friend SimpleRequest;
// Evaluation.
Expr *evaluate(Evaluator &evaluator, PatternBindingDecl *PBD,
unsigned i) const;
public:
// Separate caching.
bool isCached() const { return true; }
llvm::Optional<Expr *> getCachedResult() const;
void cacheResult(Expr *expr) const;
};
class NamingPatternRequest class NamingPatternRequest
: public SimpleRequest<NamingPatternRequest, NamedPattern *(VarDecl *), : public SimpleRequest<NamingPatternRequest, NamedPattern *(VarDecl *),
RequestFlags::SeparatelyCached> { RequestFlags::SeparatelyCached> {

View File

@@ -258,6 +258,9 @@ SWIFT_REQUEST(TypeChecker, OverriddenDeclsRequest,
SWIFT_REQUEST(TypeChecker, PatternBindingEntryRequest, SWIFT_REQUEST(TypeChecker, PatternBindingEntryRequest,
const PatternBindingEntry *(PatternBindingDecl *, unsigned, bool), const PatternBindingEntry *(PatternBindingDecl *, unsigned, bool),
SeparatelyCached, NoLocationInfo) SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, PatternBindingCheckedExecutableInitRequest,
Expr *(PatternBindingDecl *, unsigned),
SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, PrimarySourceFilesRequest, SWIFT_REQUEST(TypeChecker, PrimarySourceFilesRequest,
ArrayRef<SourceFile *>(ModuleDecl *), Cached, NoLocationInfo) ArrayRef<SourceFile *>(ModuleDecl *), Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, PropertyWrapperAuxiliaryVariablesRequest, SWIFT_REQUEST(TypeChecker, PropertyWrapperAuxiliaryVariablesRequest,

View File

@@ -2198,6 +2198,13 @@ PatternBindingDecl::getInitializerIsolation(unsigned i) const {
return var->getInitializerIsolation(); return var->getInitializerIsolation();
} }
Expr *PatternBindingDecl::getCheckedExecutableInit(unsigned i) const {
return evaluateOrDefault(getASTContext().evaluator,
PatternBindingCheckedExecutableInitRequest{
const_cast<PatternBindingDecl *>(this), i},
nullptr);
}
bool PatternBindingDecl::hasStorage() const { bool PatternBindingDecl::hasStorage() const {
// Walk the pattern, to check to see if any of the VarDecls included in it // Walk the pattern, to check to see if any of the VarDecls included in it
// have storage. // have storage.

View File

@@ -1022,6 +1022,26 @@ void PatternBindingEntryRequest::cacheResult(
PBD->getMutablePatternList()[idx].setFullyValidated(); PBD->getMutablePatternList()[idx].setFullyValidated();
} }
//----------------------------------------------------------------------------//
// PatternCheckedExecutableInitRequest computation.
//----------------------------------------------------------------------------//
llvm::Optional<Expr *>
PatternBindingCheckedExecutableInitRequest::getCachedResult() const {
auto *PBD = std::get<0>(getStorage());
auto idx = std::get<1>(getStorage());
if (!PBD->getPattern(idx)->executableInitChecked())
return llvm::None;
return PBD->getExecutableInit(idx);
}
void PatternBindingCheckedExecutableInitRequest::cacheResult(Expr *expr) const {
auto *PBD = std::get<0>(getStorage());
auto idx = std::get<1>(getStorage());
assert(expr == PBD->getExecutableInit(idx));
PBD->getPattern(idx)->setExecutableInitChecked();
}
//----------------------------------------------------------------------------// //----------------------------------------------------------------------------//
// NamingPatternRequest computation. // NamingPatternRequest computation.
//----------------------------------------------------------------------------// //----------------------------------------------------------------------------//

View File

@@ -1656,6 +1656,10 @@ emitStoredPropertyInitialization(PatternBindingDecl *pbd, unsigned i) {
!pbd->getAnchoringVarDecl(i)->isInitExposedToClients()) !pbd->getAnchoringVarDecl(i)->isInitExposedToClients())
return; return;
// Force the executable init to be type checked before emission.
if (!pbd->getCheckedExecutableInit(i))
return;
auto *var = pbd->getAnchoringVarDecl(i); auto *var = pbd->getAnchoringVarDecl(i);
SILDeclRef constant(var, SILDeclRef::Kind::StoredPropertyInitializer); SILDeclRef constant(var, SILDeclRef::Kind::StoredPropertyInitializer);
emitOrDelayFunction(constant); emitOrDelayFunction(constant);
@@ -1666,6 +1670,9 @@ emitPropertyWrapperBackingInitializer(VarDecl *var) {
auto initInfo = var->getPropertyWrapperInitializerInfo(); auto initInfo = var->getPropertyWrapperInitializerInfo();
if (initInfo.hasInitFromWrappedValue()) { if (initInfo.hasInitFromWrappedValue()) {
// FIXME: Fully typecheck the original property's init expression on-demand
// for lazy typechecking mode.
SILDeclRef constant(var, SILDeclRef::Kind::PropertyWrapperBackingInitializer); SILDeclRef constant(var, SILDeclRef::Kind::PropertyWrapperBackingInitializer);
emitOrDelayFunction(constant); emitOrDelayFunction(constant);
} }

View File

@@ -219,6 +219,10 @@ void SILGenModule::emitGlobalInitialization(PatternBindingDecl *pd,
->areAllParamsConcrete()); ->areAllParamsConcrete());
} }
// Force the executable init to be type checked before emission.
if (!pd->getCheckedExecutableInit(pbdEntry))
return;
Mangle::ASTMangler TokenMangler; Mangle::ASTMangler TokenMangler;
std::string onceTokenBuffer = TokenMangler.mangleGlobalInit(pd, pbdEntry, std::string onceTokenBuffer = TokenMangler.mangleGlobalInit(pd, pbdEntry,
false); false);

View File

@@ -2662,22 +2662,9 @@ public:
// as a replacement. // as a replacement.
diagnoseWrittenPlaceholderTypes(Ctx, PBD->getPattern(i), init); diagnoseWrittenPlaceholderTypes(Ctx, PBD->getPattern(i), init);
// If we entered an initializer context, contextualize any // Trigger a request that will complete typechecking for the
// auto-closures we might have created. // initializer.
// Note that we don't contextualize the initializer for a property (void)PBD->getCheckedExecutableInit(i);
// with a wrapper, because the initializer will have been subsumed
// by the backing storage property.
if (!DC->isLocalContext() &&
!(PBD->getSingleVar() &&
PBD->getSingleVar()->hasAttachedPropertyWrapper())) {
auto *initContext = cast_or_null<PatternBindingInitializer>(
PBD->getInitContext(i));
if (initContext) {
TypeChecker::contextualizeInitializer(initContext, init);
(void)PBD->getInitializerIsolation(i);
TypeChecker::checkInitializerEffects(initContext, init);
}
}
} }
} }
@@ -2686,6 +2673,7 @@ public:
// If this is an init accessor property with a default initializer, // If this is an init accessor property with a default initializer,
// make sure that it subsumes initializers of all of its "initializes" // make sure that it subsumes initializers of all of its "initializes"
// stored properties. // stored properties.
// FIXME: This should be requestified.
auto *initAccessor = var->getAccessor(AccessorKind::Init); auto *initAccessor = var->getAccessor(AccessorKind::Init);
if (initAccessor && PBD->isInitialized(0)) { if (initAccessor && PBD->isInitialized(0)) {
for (auto *property : initAccessor->getInitializedProperties()) { for (auto *property : initAccessor->getInitializedProperties()) {

View File

@@ -585,6 +585,43 @@ const PatternBindingEntry *PatternBindingEntryRequest::evaluate(
return &pbe; return &pbe;
} }
Expr *PatternBindingCheckedExecutableInitRequest::evaluate(
Evaluator &eval, PatternBindingDecl *binding, unsigned i) const {
// Force the entry to be checked.
(void)binding->getCheckedPatternBindingEntry(i);
if (binding->isInvalid())
return nullptr;
if (!binding->isInitialized(i))
return nullptr;
if (!binding->isInitializerChecked(i))
TypeChecker::typeCheckPatternBinding(binding, i);
if (binding->isInvalid())
return nullptr;
// If we entered an initializer context, contextualize any auto-closures we
// might have created. Note that we don't contextualize the initializer for a
// property with a wrapper, because the initializer will have been subsumed by
// the backing storage property.
auto *init = binding->getInit(i);
if (!binding->getDeclContext()->isLocalContext() &&
!(binding->getSingleVar() &&
binding->getSingleVar()->hasAttachedPropertyWrapper())) {
auto *initContext =
cast_or_null<PatternBindingInitializer>(binding->getInitContext(i));
if (initContext) {
TypeChecker::contextualizeInitializer(initContext, init);
(void)binding->getInitializerIsolation(i);
TypeChecker::checkInitializerEffects(initContext, init);
}
}
return binding->getExecutableInit(i);
}
bool bool
IsGetterMutatingRequest::evaluate(Evaluator &evaluator, IsGetterMutatingRequest::evaluate(Evaluator &evaluator,
AbstractStorageDecl *storage) const { AbstractStorageDecl *storage) const {

View File

@@ -87,7 +87,7 @@ struct InternalWrapper {
// MARK: - Global vars // MARK: - Global vars
public var publicGlobalVar: Int = 0 public var publicGlobalVar: Int = NoTypecheck.int
public var publicGlobalVarInferredType = "" public var publicGlobalVarInferredType = ""
public var (publicGlobalVarInferredTuplePatX, publicGlobalVarInferredTuplePatY) = (0, 1) public var (publicGlobalVarInferredTuplePatX, publicGlobalVarInferredTuplePatY) = (0, 1)
@@ -131,16 +131,17 @@ protocol InternalProtoConformingToPublicProto: PublicProto {
} }
public struct PublicStruct { public struct PublicStruct {
public var publicProperty: Int public var publicProperty: Int = NoTypecheck.int
public var publicPropertyInferredType = "" public var publicPropertyInferredType = ""
@PublicWrapper public var publicWrappedProperty = 3.14 @PublicWrapper public var publicWrappedProperty = 3.14
@_transparent public var publicTransparentProperty: Int { @_transparent public var publicTransparentProperty: Int {
get { return 1 } get { return 1 }
} }
public dynamic var publicDynamicProperty: Int = 5 public dynamic var publicDynamicProperty: Int = 5
public static let publicStaticProperty: Int = NoTypecheck.int
public static let publicStaticPropertyInferred = 2
public init(x: Int) { public init(x: Int) {
self.publicProperty = 1
_ = NoTypecheck() _ = NoTypecheck()
} }
@@ -189,9 +190,11 @@ struct InternalStruct: NoTypecheckProto {
} }
public class PublicClass { public class PublicClass {
public var publicProperty: Int public var publicProperty: Int = NoTypecheck.int
public var publicPropertyInferredType = "" public var publicPropertyInferredType = ""
@PublicWrapper public final var publicFinalWrappedProperty: Bool = false @PublicWrapper public final var publicFinalWrappedProperty: Bool = false
public static let publicStaticProperty: Int = NoTypecheck.int
public static let publicStaticPropertyInferred = 2
public init(x: Int) { public init(x: Int) {
self.publicProperty = x self.publicProperty = x

View File

@@ -43,6 +43,8 @@ func testPublicStructs() {
let _: Int = s.publicDynamicProperty let _: Int = s.publicDynamicProperty
PublicStruct.publicStaticMethod() PublicStruct.publicStaticMethod()
PublicStruct.activeMethod() PublicStruct.activeMethod()
let _: Int = PublicStruct.publicStaticProperty
let _: Int = PublicStruct.publicStaticPropertyInferred
let _ = FrozenPublicStruct(1) let _ = FrozenPublicStruct(1)
} }
@@ -59,12 +61,16 @@ func testPublicClasses() {
let _: String = c.publicPropertyInferredType let _: String = c.publicPropertyInferredType
c.publicFinalWrappedProperty = true c.publicFinalWrappedProperty = true
PublicClass.publicClassMethod() PublicClass.publicClassMethod()
let _: Int = PublicClass.publicStaticProperty
let _: Int = PublicClass.publicStaticPropertyInferred
let d = PublicDerivedClass(x: 3) let d = PublicDerivedClass(x: 3)
let _: Int = d.publicMethod() let _: Int = d.publicMethod()
let _: Int = d.publicProperty let _: Int = d.publicProperty
let _: String = d.publicPropertyInferredType let _: String = d.publicPropertyInferredType
PublicDerivedClass.publicClassMethod() PublicDerivedClass.publicClassMethod()
let _: Int = PublicDerivedClass.publicStaticProperty
let _: Int = PublicDerivedClass.publicStaticPropertyInferred
class DerivedFromPublicClassSynthesizedDesignatedInit: PublicClassSynthesizedDesignatedInit { class DerivedFromPublicClassSynthesizedDesignatedInit: PublicClassSynthesizedDesignatedInit {
init() {} init() {}

View File

@@ -0,0 +1,60 @@
// RUN: %target-swift-frontend -emit-silgen %s -parse-as-library -module-name Test | %FileCheck %s --check-prefixes=CHECK,CHECK-NON-LAZY
// RUN: %target-swift-frontend -emit-silgen %s -parse-as-library -module-name Test -experimental-lazy-typecheck | %FileCheck %s --check-prefixes=CHECK,CHECK-LAZY
enum E {
case a, b
}
func internalFunc(_ e: E = .a) -> Int {
switch e {
case .a: return 1
case .b: return 2
}
}
// CHECK-LABEL: sil private [global_init_once_fn]{{.*}} @$s4Test9globalVar_WZ : $@convention(c) (Builtin.RawPointer) -> () {
public var globalVar = internalFunc()
public struct S {
// CHECK-LABEL: sil [transparent]{{.*}} @$s4Test1SV11instanceVarSivpfi : $@convention(thin) () -> Int {
public var instanceVar = internalFunc()
// CHECK-LABEL: sil [transparent]{{.*}} @$s4Test1SV12instanceVar2Sivpfi : $@convention(thin) () -> Int {
public var instanceVar2 = internalFunc(.b)
// FIXME: This initializer should be subsumed.
// CHECK-LAZY: sil [transparent] [ossa] @$s4Test1SV15lazyInstanceVarSivpfi : $@convention(thin) () -> Int {
// CHECK-NON-LAZY-NOT: sil [transparent] [ossa] @$s4Test1SV15lazyInstanceVarSivpfi : $@convention(thin) () -> Int {
// CHECK-LABEL: sil [transparent]{{.*}} @$s4Test1SV018$__lazy_storage_$_B11InstanceVar33_0E4F053AA3AB7D4CDE3A37DBA8EF0430LLSiSgvpfi : $@convention(thin) () -> Optional<Int> {
public lazy var lazyInstanceVar = internalFunc()
// CHECK-LABEL: sil private [global_init_once_fn]{{.*}} @$s4Test1SV9staticVar_WZ : $@convention(c) (Builtin.RawPointer) -> () {
public static var staticVar = internalFunc()
// FIXME: This initializer should be subsumed.
// CHECK-LAZY: sil [transparent] [ossa] @$s4Test1SV15subsumedInitVarSivpfi : $@convention(thin) () -> Int {
// CHECK-NON-LAZY-NOT: sil [transparent] [ossa] @$s4Test1SV15subsumedInitVarSivpfi : $@convention(thin) () -> Int {
public var subsumedInitVar = internalFunc()
// CHECK-LABEL: sil [transparent] [ossa] @$s4Test1SV19varWithInitAccessorSivpfi : $@convention(thin) () -> Int {
public var varWithInitAccessor: Int = internalFunc() {
@storageRestrictions(initializes: subsumedInitVar)
init {
subsumedInitVar = newValue
}
get { subsumedInitVar }
}
// FIXME: Test vars with property wrappers.
}
extension S {
// CHECK-LABEL: sil private [global_init_once_fn]{{.*}} @$s4Test1SV20staticVarInExtension_WZ : $@convention(c) (Builtin.RawPointer) -> () {
public static var staticVarInExtension = internalFunc()
}
public func returnsS() -> S {
// Force the synthesized initializer for S to be emitted.
// CHECK: function_ref @$s4Test1SVACycfC : $@convention(method) (@thin S.Type) -> S
return S()
}