Skip to content

[Sema/SILGen/IRGen/StdLib] Implement metatype keypaths #73242

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 16 commits into from
Sep 28, 2024
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
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ EXPERIMENTAL_FEATURE(TupleConformances, false)
EXPERIMENTAL_FEATURE(FullTypedThrows, false)
EXPERIMENTAL_FEATURE(SameElementRequirements, false)
EXPERIMENTAL_FEATURE(GlobalActorInferenceCutoff, false)
EXPERIMENTAL_FEATURE(KeyPathWithStaticMembers, false)

// Whether to enable @_used and @_section attributes
EXPERIMENTAL_FEATURE(SymbolLinkageMarkers, true)
Expand Down
2 changes: 2 additions & 0 deletions include/swift/SIL/SILProperty.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class SILProperty : public llvm::ilist_node<SILProperty>,
return Component;
}

CanType getBaseType() const;

void print(SILPrintContext &Ctx) const;
void dump() const;

Expand Down
17 changes: 10 additions & 7 deletions include/swift/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -2026,7 +2026,8 @@ class TreatKeyPathSubscriptIndexAsHashable final : public ConstraintFix {

class AllowInvalidRefInKeyPath final : public ConstraintFix {
enum RefKind {
// Allow a reference to a static member as a key path component.
// Allow a reference to a static member as a key path component if it is
// declared in a module with built with Swift 6.0 compiler version or older.
StaticMember,
// Allow a reference to a declaration with mutating getter as
// a key path component.
Expand All @@ -2042,11 +2043,12 @@ class AllowInvalidRefInKeyPath final : public ConstraintFix {
} Kind;

ValueDecl *Member;
Type BaseType;

AllowInvalidRefInKeyPath(ConstraintSystem &cs, RefKind kind,
AllowInvalidRefInKeyPath(ConstraintSystem &cs, Type baseType, RefKind kind,
ValueDecl *member, ConstraintLocator *locator)
: ConstraintFix(cs, FixKind::AllowInvalidRefInKeyPath, locator),
Kind(kind), Member(member) {}
Kind(kind), Member(member), BaseType(baseType) {}

public:
std::string getName() const override {
Expand All @@ -2071,8 +2073,9 @@ class AllowInvalidRefInKeyPath final : public ConstraintFix {
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override;

/// Determine whether give reference requires a fix and produce one.
static AllowInvalidRefInKeyPath *
forRef(ConstraintSystem &cs, ValueDecl *member, ConstraintLocator *locator);
static AllowInvalidRefInKeyPath *forRef(ConstraintSystem &cs, Type baseType,
ValueDecl *member,
ConstraintLocator *locator);

bool isEqual(const ConstraintFix *other) const;

Expand All @@ -2081,8 +2084,8 @@ class AllowInvalidRefInKeyPath final : public ConstraintFix {
}

private:
static AllowInvalidRefInKeyPath *create(ConstraintSystem &cs, RefKind kind,
ValueDecl *member,
static AllowInvalidRefInKeyPath *create(ConstraintSystem &cs, Type baseType,
RefKind kind, ValueDecl *member,
ConstraintLocator *locator);
};

Expand Down
1 change: 1 addition & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ UNINTERESTING_FEATURE(GroupActorErrors)
UNINTERESTING_FEATURE(SameElementRequirements)
UNINTERESTING_FEATURE(UnspecifiedMeansMainActorIsolated)
UNINTERESTING_FEATURE(GlobalActorInferenceCutoff)
UNINTERESTING_FEATURE(KeyPathWithStaticMembers)

static bool usesFeatureSendingArgsAndResults(Decl *decl) {
auto isFunctionTypeWithSending = [](Type type) {
Expand Down
6 changes: 5 additions & 1 deletion lib/IRGen/Linking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,6 @@ bool LinkEntity::isWeakImported(ModuleDecl *module) const {
case Kind::ObjCMetaclass:
case Kind::SwiftMetaclassStub:
case Kind::ClassMetadataBaseOffset:
case Kind::PropertyDescriptor:
case Kind::NominalTypeDescriptor:
case Kind::NominalTypeDescriptorRecord:
case Kind::ModuleDescriptor:
Expand All @@ -1390,6 +1389,11 @@ bool LinkEntity::isWeakImported(ModuleDecl *module) const {
case Kind::OpaqueTypeDescriptorAccessorVar:
case Kind::DistributedAccessor:
return getDecl()->isWeakImported(module);

case Kind::PropertyDescriptor:
// Static properties may have nil property descriptors if declared in
// modules compiled with older compilers and should be weak linked.
return (getDecl()->isWeakImported(module) || getDecl()->isStatic());

case Kind::CanonicalSpecializedGenericSwiftMetaclassStub:
return getType()->getClassOrBoundGenericClass()->isWeakImported(module);
Expand Down
15 changes: 10 additions & 5 deletions lib/SIL/IR/SIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,16 +361,21 @@ bool AbstractStorageDecl::exportsPropertyDescriptor() const {
}
}
}
// TODO: Global and static properties ought to eventually be referenceable
// as key paths from () or T.Type too.
if (!getDeclContext()->isTypeContext() || isStatic())

// TODO: Global properties ought to eventually be referenceable
// as key paths from ().
if (!getDeclContext()->isTypeContext())
return false;

// Protocol requirements do not need property descriptors.
if (isa<ProtocolDecl>(getDeclContext()))
return false;


// Static properties in protocol extensions do not need
// descriptors as existential Any.Type will not resolve to a value.
if (isStatic() && getDeclContext()->getSelfProtocolDecl())
return false;

// FIXME: We should support properties and subscripts with '_read' accessors;
// 'get' is not part of the opaque accessor set there.
auto *getter = getOpaqueAccessor(AccessorKind::Get);
Expand Down
32 changes: 24 additions & 8 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7348,24 +7348,39 @@ void SILFunction::verifySILUndefMap() const {
}
}

CanType SILProperty::getBaseType() const {
auto *decl = getDecl();
auto *dc = decl->getInnermostDeclContext();

// TODO: base type for global descriptors
auto sig = dc->getGenericSignatureOfContext();
auto baseTy =
dc->getInnermostTypeContext()->getSelfInterfaceType()->getReducedType(
sig);
if (decl->isStatic())
baseTy = CanMetatypeType::get(baseTy);

if (sig) {
auto env = dc->getGenericEnvironmentOfContext();
baseTy = env->mapTypeIntoContext(baseTy)->getCanonicalType();
}

return baseTy;
}

/// Verify that a property descriptor follows invariants.
void SILProperty::verify(const SILModule &M) const {
if (!verificationEnabled(M))
return;

auto *decl = getDecl();
auto *dc = decl->getInnermostDeclContext();

// TODO: base type for global/static descriptors
auto sig = dc->getGenericSignatureOfContext();
auto baseTy = dc->getInnermostTypeContext()->getSelfInterfaceType()
->getReducedType(sig);
auto sig = decl->getInnermostDeclContext()->getGenericSignatureOfContext();
auto leafTy = decl->getValueInterfaceType()->getReducedType(sig);
SubstitutionMap subs;
if (sig) {
auto env = dc->getGenericEnvironmentOfContext();
auto env =
decl->getInnermostDeclContext()->getGenericEnvironmentOfContext();
subs = env->getForwardingSubstitutionMap();
baseTy = env->mapTypeIntoContext(baseTy)->getCanonicalType();
leafTy = env->mapTypeIntoContext(leafTy)->getCanonicalType();
}
bool hasIndices = false;
Expand All @@ -7386,6 +7401,7 @@ void SILProperty::verify(const SILModule &M) const {
auto typeExpansionContext =
TypeExpansionContext::noOpaqueTypeArchetypesSubstitution(
ResilienceExpansion::Maximal);
auto baseTy = getBaseType();
verifyKeyPathComponent(const_cast<SILModule&>(M),
typeExpansionContext,
getSerializedKind(),
Expand Down
16 changes: 9 additions & 7 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1873,9 +1873,10 @@ SILGenModule::canStorageUseStoredKeyPathComponent(AbstractStorageDecl *decl,
expansion);
switch (strategy.getKind()) {
case AccessStrategy::Storage: {
// Keypaths rely on accessors to handle the special behavior of weak or
// unowned properties.
if (decl->getInterfaceType()->is<ReferenceStorageType>())
// Keypaths rely on accessors to handle the special behavior of weak,
// unowned, or static properties.
if (decl->getInterfaceType()->is<ReferenceStorageType>() ||
decl->isStatic())
return false;

// If the field offset depends on the generic instantiation, we have to
Expand Down Expand Up @@ -1973,13 +1974,14 @@ void SILGenModule::tryEmitPropertyDescriptor(AbstractStorageDecl *decl) {

Type baseTy;
if (decl->getDeclContext()->isTypeContext()) {
// TODO: Static properties should eventually be referenceable as
// keypaths from T.Type -> Element, viz `baseTy = MetatypeType::get(baseTy)`
assert(!decl->isStatic());


baseTy = decl->getDeclContext()->getSelfInterfaceType()
->getReducedType(decl->getInnermostDeclContext()
->getGenericSignatureOfContext());

if (decl->isStatic()) {
baseTy = MetatypeType::get(baseTy);
}
} else {
// TODO: Global variables should eventually be referenceable as
// key paths from (), viz. baseTy = TupleType::getEmpty(getASTContext());
Expand Down
44 changes: 30 additions & 14 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3174,6 +3174,11 @@ emitKeyPathRValueBase(SILGenFunction &subSGF,
auto paramSubstValue = subSGF.emitOrigToSubstValue(loc, paramOrigValue,
AbstractionPattern::getOpaque(),
baseType);

// If base is a metatype, it cannot be opened as an existential or upcasted
// from a class.
if (baseType->is<MetatypeType>())
return paramSubstValue;

// Pop open an existential container base.
if (baseType->isAnyExistentialType()) {
Expand Down Expand Up @@ -4011,9 +4016,30 @@ getIdForKeyPathComponentComputedProperty(SILGenModule &SGM,
AbstractStorageDecl *storage,
ResilienceExpansion expansion,
AccessStrategy strategy) {
auto getAccessorFunction = [&SGM](AbstractStorageDecl *storage,
bool isForeign) -> SILFunction * {
// Identify the property using its (unthunked) getter. For a
// computed property, this should be stable ABI; for a resilient public
// property, this should also be stable ABI across modules.
auto representativeDecl = getRepresentativeAccessorForKeyPath(storage);
// If the property came from an import-as-member function defined in C,
// use the original C function as the key.
auto ref =
SILDeclRef(representativeDecl, SILDeclRef::Kind::Func, isForeign);
// TODO: If the getter has shared linkage (say it's synthesized for a
// Clang-imported thing), we'll need some other sort of
// stable identifier.
return SGM.getFunction(ref, NotForDefinition);
};

switch (strategy.getKind()) {
case AccessStrategy::Storage:
// Identify reabstracted stored properties by the property itself.
if (auto decl = cast<VarDecl>(storage); decl->isStatic()) {
// For metatype keypaths, identify property via accessors.
return getAccessorFunction(storage, /*isForeign=*/false);
}
// Otherwise, identify reabstracted stored properties by the property
// itself.
return cast<VarDecl>(storage);
case AccessStrategy::MaterializeToTemporary:
// Use the read strategy. But try to avoid turning e.g. an
Expand All @@ -4026,19 +4052,9 @@ getIdForKeyPathComponentComputedProperty(SILGenModule &SGM,
}
LLVM_FALLTHROUGH;
case AccessStrategy::DirectToAccessor: {
// Identify the property using its (unthunked) getter. For a
// computed property, this should be stable ABI; for a resilient public
// property, this should also be stable ABI across modules.
auto representativeDecl = getRepresentativeAccessorForKeyPath(storage);
// If the property came from an import-as-member function defined in C,
// use the original C function as the key.
bool isForeign = representativeDecl->isImportAsMember();
auto getterRef = SILDeclRef(representativeDecl,
SILDeclRef::Kind::Func, isForeign);
// TODO: If the getter has shared linkage (say it's synthesized for a
// Clang-imported thing), we'll need some other sort of
// stable identifier.
return SGM.getFunction(getterRef, NotForDefinition);
return getAccessorFunction(
storage,
getRepresentativeAccessorForKeyPath(storage)->isImportAsMember());
}
case AccessStrategy::DispatchToAccessor: {
// Identify the property by its vtable or wtable slot.
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1247,10 +1247,10 @@ class SILGenType : public TypeMemberVisitor<SILGenType> {

void visitVarDecl(VarDecl *vd) {
// Collect global variables for static properties.
// FIXME: We can't statically emit a global variable for generic properties.
if (vd->isStatic() && vd->hasStorage()) {
emitTypeMemberGlobalVariable(SGM, vd);
visitAccessors(vd);
SGM.tryEmitPropertyDescriptor(vd);
return;
}

Expand Down
3 changes: 0 additions & 3 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5384,9 +5384,6 @@ namespace {
auto varDecl = cast<VarDecl>(property);
// Key paths don't work with mutating-get properties.
assert(!varDecl->isGetterMutating());
// Key paths don't currently support static members.
// There is a fix which diagnoses such situation already.
assert(!varDecl->isStatic());

// Compute the concrete reference to the member.
auto ref = resolveConcreteDeclRef(property, locator);
Expand Down
27 changes: 22 additions & 5 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4993,10 +4993,10 @@ bool AllowTypeOrInstanceMemberFailure::diagnoseAsError() {
}

// If this is a reference to a static member by one of the key path
// components, let's provide a tailored diagnostic and return because
// that is unsupported so there is no fix-it.
// components, let's provide a tailored diagnostic with fix-it.
if (locator->isInKeyPathComponent()) {
InvalidStaticMemberRefInKeyPath failure(getSolution(), Member, locator);
InvalidStaticMemberRefInKeyPath failure(getSolution(), BaseType, Member,
locator);
return failure.diagnoseAsError();
}

Expand Down Expand Up @@ -6335,8 +6335,25 @@ SourceLoc InvalidMemberRefInKeyPath::getLoc() const {
}

bool InvalidStaticMemberRefInKeyPath::diagnoseAsError() {
emitDiagnostic(diag::expr_keypath_static_member, getMember(),
isForKeyPathDynamicMemberLookup());
auto *KPE = getAsExpr<KeyPathExpr>(getRawAnchor());
auto rootTyRepr = KPE->getExplicitRootType();
auto isProtocol = getBaseType()->isExistentialType();

if (!getConstraintSystem().getASTContext().LangOpts.hasFeature(
Feature::KeyPathWithStaticMembers)) {
emitDiagnostic(diag::expr_keypath_static_member, getMember(),
isForKeyPathDynamicMemberLookup());
} else {
if (rootTyRepr && !isProtocol) {
emitDiagnostic(diag::could_not_use_type_member_on_instance, getBaseType(),
DeclNameRef(getMember()->getName()))
.fixItInsert(rootTyRepr->getEndLoc(), ".Type");
} else {
emitDiagnostic(diag::could_not_use_type_member_on_instance, getBaseType(),
DeclNameRef(getMember()->getName()));
}
}

return true;
}

Expand Down
15 changes: 10 additions & 5 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -1751,20 +1751,25 @@ class InvalidMemberRefInKeyPath : public FailureDiagnostic {
};

/// Diagnose an attempt to reference a static member as a key path component
/// e.g.
/// without .Type e.g.
///
/// ```swift
/// struct S {
/// static var foo: Int = 42
/// }
///
/// _ = \S.Type.foo
/// _ = \S.foo
/// ```
class InvalidStaticMemberRefInKeyPath final : public InvalidMemberRefInKeyPath {
Type BaseType;

public:
InvalidStaticMemberRefInKeyPath(const Solution &solution, ValueDecl *member,
ConstraintLocator *locator)
: InvalidMemberRefInKeyPath(solution, member, locator) {}
InvalidStaticMemberRefInKeyPath(const Solution &solution, Type baseType,
ValueDecl *member, ConstraintLocator *locator)
: InvalidMemberRefInKeyPath(solution, member, locator),
BaseType(baseType->getRValueType()) {}

Type getBaseType() const { return BaseType; }

bool diagnoseAsError() override;
};
Expand Down
Loading