mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Implement SE-0195, which introduces "Dynamic Member Lookup" Types (#14546)
* Implement the recently accepted SE-0195 proposal, which introduces "Dynamic Member Lookup" Types. This is a dusted off and updated version of PR13361, which switches from DynamicMemberLookupProtocol to @dynamicMemberLookup as was requested by the final review decision. This also rebases it, updates it for other changes in the compiler, fixes a bunch of bugs, and adds support for keypaths. Thank you to @rudx and @DougGregor in particular for the helpful review comments and test cases!
This commit is contained in:
@@ -111,7 +111,8 @@ SIMPLE_DECL_ATTR(noreturn, NoReturn, OnFunc, 7)
|
||||
|
||||
SIMPLE_DECL_ATTR(_exported, Exported, OnImport | UserInaccessible, 8)
|
||||
|
||||
/// NOTE: 9 is unused.
|
||||
SIMPLE_DECL_ATTR(dynamicMemberLookup, DynamicMemberLookup,
|
||||
OnClass | OnStruct | OnEnum | OnProtocol, 9)
|
||||
|
||||
SIMPLE_DECL_ATTR(NSCopying, NSCopying,
|
||||
OnVar, 10)
|
||||
|
||||
@@ -962,6 +962,9 @@ NOTE(archetype_declared_in_type,none,
|
||||
NOTE(unbound_generic_parameter_explicit_fix,none,
|
||||
"explicitly specify the generic arguments to fix this issue", ())
|
||||
|
||||
ERROR(type_invalid_dml,none,
|
||||
"@dynamicMemberLookup attribute requires %0 to have a "
|
||||
"'subscript(dynamicMember:)' member with a string index", (Type))
|
||||
|
||||
ERROR(string_index_not_integer,none,
|
||||
"String must not be indexed with %0, it has variable size elements",
|
||||
@@ -2857,6 +2860,8 @@ ERROR(assignment_lhs_is_immutable_property,none,
|
||||
"cannot assign to property: %0", (StringRef))
|
||||
ERROR(assignment_subscript_has_immutable_base,none,
|
||||
"cannot assign through subscript: %0", (StringRef))
|
||||
ERROR(assignment_dynamic_property_has_immutable_base,none,
|
||||
"cannot assign through dynamic lookup property: %0", (StringRef))
|
||||
ERROR(assignment_bang_has_immutable_subcomponent,none,
|
||||
"cannot assign through '!': %0", (StringRef))
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ IDENTIFIER(decode)
|
||||
IDENTIFIER(decodeIfPresent)
|
||||
IDENTIFIER(Decoder)
|
||||
IDENTIFIER(decoder)
|
||||
IDENTIFIER(dynamicMember)
|
||||
IDENTIFIER(Element)
|
||||
IDENTIFIER(Encodable)
|
||||
IDENTIFIER(encode)
|
||||
|
||||
@@ -401,6 +401,29 @@ diagnoseInvalidDynamicConstructorReferences(ConstraintSystem &cs,
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Form a type checked expression for the index of a @dynamicMemberLookup
|
||||
/// subscript index expression. This will have tuple type of (dynamicMember:T).
|
||||
static Expr *getDMLIndexExpr(StringRef name, Type ty, SourceLoc loc,
|
||||
DeclContext *dc, ConstraintSystem &cs) {
|
||||
auto &ctx = cs.TC.Context;
|
||||
|
||||
// Build and type check the string literal index value to the specific
|
||||
// string type expected by the subscript.
|
||||
Expr *nameExpr = new (ctx)
|
||||
StringLiteralExpr(name, loc, /*implicit*/true);
|
||||
|
||||
|
||||
// Build a tuple so that argument has a label.
|
||||
Expr *tuple = TupleExpr::create(ctx, loc, nameExpr, ctx.Id_dynamicMember, loc,
|
||||
loc, /*hasTrailingClosure*/false,
|
||||
/*implicit*/true);
|
||||
(void)cs.TC.typeCheckExpression(tuple, dc, TypeLoc::withoutLoc(ty),
|
||||
CTP_CallArgument);
|
||||
cs.cacheExprTypes(tuple);
|
||||
return tuple;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/// \brief Rewrites an expression by applying the solution of a constraint
|
||||
@@ -1370,10 +1393,12 @@ namespace {
|
||||
ArrayRef<Identifier> argLabels,
|
||||
bool hasTrailingClosure,
|
||||
ConstraintLocatorBuilder locator, bool isImplicit,
|
||||
AccessSemantics semantics) {
|
||||
AccessSemantics semantics,
|
||||
Optional<SelectedOverload> selected = None) {
|
||||
|
||||
// Determine the declaration selected for this subscript operation.
|
||||
auto selected = getOverloadChoiceIfAvailable(
|
||||
if (!selected)
|
||||
selected = getOverloadChoiceIfAvailable(
|
||||
cs.getConstraintLocator(
|
||||
locator.withPathElement(
|
||||
ConstraintLocator::SubscriptMember)));
|
||||
@@ -1418,10 +1443,16 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
if (selected->choice.isDecl())
|
||||
if (selected->choice.isDecl()) {
|
||||
auto locatorKind = ConstraintLocator::SubscriptMember;
|
||||
if (selected->choice.getKind() ==
|
||||
OverloadChoiceKind::DynamicMemberLookup)
|
||||
locatorKind = ConstraintLocator::Member;
|
||||
|
||||
newSubscript = forceUnwrapIfExpected(
|
||||
newSubscript, selected->choice.getDecl(),
|
||||
locator.withPathElement(ConstraintLocator::SubscriptMember));
|
||||
locator.withPathElement(locatorKind));
|
||||
}
|
||||
|
||||
return newSubscript;
|
||||
}
|
||||
@@ -1528,22 +1559,29 @@ namespace {
|
||||
// Check whether the base is 'super'.
|
||||
bool isSuper = base->isSuperExpr();
|
||||
|
||||
// Figure out the index and result types.
|
||||
auto subscriptTy = simplifyType(selected.openedType);
|
||||
auto subscriptFnTy = subscriptTy->castTo<AnyFunctionType>();
|
||||
auto resultTy = subscriptFnTy->getResult();
|
||||
// Use the correct kind of locator depending on how this subscript came
|
||||
// to be.
|
||||
auto locatorKind = ConstraintLocator::SubscriptMember;
|
||||
if (choice.getKind() == OverloadChoiceKind::DynamicMemberLookup)
|
||||
locatorKind = ConstraintLocator::Member;
|
||||
|
||||
// If we opened up an existential when performing the subscript, open
|
||||
// the base accordingly.
|
||||
auto knownOpened = solution.OpenedExistentialTypes.find(
|
||||
getConstraintSystem().getConstraintLocator(
|
||||
locator.withPathElement(
|
||||
ConstraintLocator::SubscriptMember)));
|
||||
locator.withPathElement(locatorKind)));
|
||||
if (knownOpened != solution.OpenedExistentialTypes.end()) {
|
||||
base = openExistentialReference(base, knownOpened->second, subscript);
|
||||
baseTy = knownOpened->second;
|
||||
}
|
||||
|
||||
// Figure out the index and result types.
|
||||
Type resultTy;
|
||||
if (choice.getKind() != OverloadChoiceKind::DynamicMemberLookup) {
|
||||
auto subscriptTy = simplifyType(selected.openedType);
|
||||
auto *subscriptFnTy = subscriptTy->castTo<FunctionType>();
|
||||
resultTy = subscriptFnTy->getResult();
|
||||
|
||||
// Coerce the index argument.
|
||||
index = coerceCallArguments(index, subscriptFnTy, nullptr,
|
||||
argLabels, hasTrailingClosure,
|
||||
@@ -1552,6 +1590,14 @@ namespace {
|
||||
if (!index)
|
||||
return nullptr;
|
||||
|
||||
} else {
|
||||
// If this is a @dynamicMemberLookup, then the type of the selection is
|
||||
// actually the property/result type. That's fine though, and we
|
||||
// already have the index type adjusted to the correct type expected by
|
||||
// the subscript.
|
||||
resultTy = simplifyType(selected.openedType);
|
||||
}
|
||||
|
||||
auto getType = [&](const Expr *E) -> Type {
|
||||
return cs.getType(E);
|
||||
};
|
||||
@@ -1562,7 +1608,7 @@ namespace {
|
||||
SmallVector<Substitution, 4> substitutions;
|
||||
solution.computeSubstitutions(
|
||||
subscript->getInnermostDeclContext()->getGenericSignatureOfContext(),
|
||||
locator.withPathElement(ConstraintLocator::SubscriptMember),
|
||||
locator.withPathElement(locatorKind),
|
||||
substitutions);
|
||||
ConcreteDeclRef subscriptRef(tc.Context, subscript, substitutions);
|
||||
|
||||
@@ -2737,7 +2783,8 @@ namespace {
|
||||
// before taking a single element.
|
||||
auto baseTy = cs.getType(base);
|
||||
if (!toType->hasLValueType() && baseTy->hasLValueType())
|
||||
base = coerceToType(base, baseTy->getRValueType(), cs.getConstraintLocator(base));
|
||||
base = coerceToType(base, baseTy->getRValueType(),
|
||||
cs.getConstraintLocator(base));
|
||||
|
||||
return cs.cacheType(new (cs.getASTContext())
|
||||
TupleElementExpr(base, dotLoc,
|
||||
@@ -2745,12 +2792,42 @@ namespace {
|
||||
nameLoc.getBaseNameLoc(), toType));
|
||||
}
|
||||
|
||||
case OverloadChoiceKind::BaseType: {
|
||||
case OverloadChoiceKind::BaseType:
|
||||
return base;
|
||||
}
|
||||
|
||||
case OverloadChoiceKind::KeyPathApplication:
|
||||
llvm_unreachable("should only happen in a subscript");
|
||||
|
||||
case OverloadChoiceKind::DynamicMemberLookup: {
|
||||
// Application of a DynamicMemberLookup result turns a member access of
|
||||
// x.foo into x[dynamicMember: "foo"].
|
||||
auto &ctx = cs.getASTContext();
|
||||
auto loc = nameLoc.getStartLoc();
|
||||
|
||||
// Figure out the expected type of the string. We know the
|
||||
// openedFullType will be "xType -> indexType -> resultType". Dig out
|
||||
// its index type.
|
||||
auto declTy = solution.simplifyType(selected.openedFullType);
|
||||
auto subscriptTy = declTy->castTo<FunctionType>()->getResult();
|
||||
auto refFnType = subscriptTy->castTo<FunctionType>();
|
||||
assert(refFnType->getParams().size() == 1 &&
|
||||
"subscript always has one arg");
|
||||
auto stringType = refFnType->getParams()[0].getPlainType();
|
||||
auto tupleTy = TupleType::get(TupleTypeElt(stringType,
|
||||
ctx.Id_dynamicMember), ctx);
|
||||
|
||||
// Build and type check the string literal index value to the specific
|
||||
// string type expected by the subscript.
|
||||
auto fieldName = selected.choice.getName().getBaseIdentifier().str();
|
||||
auto index = getDMLIndexExpr(fieldName, tupleTy, loc, dc, cs);
|
||||
|
||||
// Build and return a subscript that uses this string as the index.
|
||||
return buildSubscript(base, index, ctx.Id_dynamicMember,
|
||||
/*trailingClosure*/false,
|
||||
cs.getConstraintLocator(expr),
|
||||
/*isImplicit*/false,
|
||||
AccessSemantics::Ordinary, selected);
|
||||
}
|
||||
}
|
||||
|
||||
llvm_unreachable("Unhandled OverloadChoiceKind in switch.");
|
||||
@@ -4154,7 +4231,7 @@ namespace {
|
||||
Type leafTy = keyPathTy->getGenericArgs()[1];
|
||||
|
||||
for (unsigned i : indices(E->getComponents())) {
|
||||
auto &origComponent = E->getComponents()[i];
|
||||
auto &origComponent = E->getMutableComponents()[i];
|
||||
|
||||
// If there were unresolved types, we may end up with a null base for
|
||||
// following components.
|
||||
@@ -4181,17 +4258,36 @@ namespace {
|
||||
return objectTy;
|
||||
};
|
||||
|
||||
KeyPathExpr::Component component;
|
||||
switch (auto kind = origComponent.getKind()) {
|
||||
case KeyPathExpr::Component::Kind::UnresolvedProperty: {
|
||||
auto kind = origComponent.getKind();
|
||||
Optional<SelectedOverload> foundDecl;
|
||||
|
||||
auto locator = cs.getConstraintLocator(E,
|
||||
ConstraintLocator::PathElement::getKeyPathComponent(i));
|
||||
auto foundDecl = getOverloadChoiceIfAvailable(locator);
|
||||
|
||||
// If this is an unresolved link, make sure we resolved it.
|
||||
if (kind == KeyPathExpr::Component::Kind::UnresolvedProperty ||
|
||||
kind == KeyPathExpr::Component::Kind::UnresolvedSubscript) {
|
||||
foundDecl = getOverloadChoiceIfAvailable(locator);
|
||||
// Leave the component unresolved if the overload was not resolved.
|
||||
if (foundDecl) {
|
||||
// If this was a @dynamicMemberLookup property, then we actually
|
||||
// form a subscript reference, so switch the kind.
|
||||
if (foundDecl->choice.getKind()
|
||||
== OverloadChoiceKind::DynamicMemberLookup) {
|
||||
kind = KeyPathExpr::Component::Kind::UnresolvedSubscript;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeyPathExpr::Component component;
|
||||
switch (kind) {
|
||||
case KeyPathExpr::Component::Kind::UnresolvedProperty: {
|
||||
// If we couldn't resolve the component, leave it alone.
|
||||
if (!foundDecl) {
|
||||
component = origComponent;
|
||||
break;
|
||||
}
|
||||
|
||||
auto property = foundDecl->choice.getDecl();
|
||||
|
||||
// Key paths can only refer to properties currently.
|
||||
@@ -4231,6 +4327,7 @@ namespace {
|
||||
resolvedTy = simplifyType(resolvedTy);
|
||||
|
||||
auto ref = ConcreteDeclRef(cs.getASTContext(), property, subs);
|
||||
|
||||
component = KeyPathExpr::Component::forProperty(ref,
|
||||
resolvedTy,
|
||||
origComponent.getLoc());
|
||||
@@ -4248,14 +4345,12 @@ namespace {
|
||||
break;
|
||||
}
|
||||
case KeyPathExpr::Component::Kind::UnresolvedSubscript: {
|
||||
auto locator = cs.getConstraintLocator(E,
|
||||
ConstraintLocator::PathElement::getKeyPathComponent(i));
|
||||
auto foundDecl = getOverloadChoiceIfAvailable(locator);
|
||||
// Leave the component unresolved if the overload was not resolved.
|
||||
if (!foundDecl) {
|
||||
component = origComponent;
|
||||
break;
|
||||
}
|
||||
|
||||
auto subscript = cast<SubscriptDecl>(foundDecl->choice.getDecl());
|
||||
if (subscript->isGetterMutating()) {
|
||||
cs.TC.diagnose(origComponent.getLoc(),
|
||||
@@ -4267,18 +4362,38 @@ namespace {
|
||||
|
||||
auto dc = subscript->getInnermostDeclContext();
|
||||
SmallVector<Substitution, 4> subs;
|
||||
SubstitutionMap subMap;
|
||||
auto indexType = subscript->getIndicesInterfaceType();
|
||||
|
||||
if (auto sig = dc->getGenericSignatureOfContext()) {
|
||||
// Compute substitutions to refer to the member.
|
||||
solution.computeSubstitutions(sig, locator, subs);
|
||||
subMap = sig->getSubstitutionMap(subs);
|
||||
indexType = indexType.subst(subMap);
|
||||
indexType = indexType.subst(sig->getSubstitutionMap(subs));
|
||||
}
|
||||
|
||||
// If this is a @dynamicMemberLookup reference to resolve a property
|
||||
// through the subscript(dynamicMember:) member, restore the
|
||||
// openedType and origComponent to its full reference as if the user
|
||||
// wrote out the subscript manually.
|
||||
if (foundDecl->choice.getKind()
|
||||
== OverloadChoiceKind::DynamicMemberLookup) {
|
||||
foundDecl->openedType = foundDecl->openedFullType
|
||||
->castTo<AnyFunctionType>()->getResult();
|
||||
|
||||
auto &ctx = cs.TC.Context;
|
||||
auto loc = origComponent.getLoc();
|
||||
auto fieldName =
|
||||
foundDecl->choice.getName().getBaseIdentifier().str();
|
||||
auto index = getDMLIndexExpr(fieldName, indexType, loc, dc, cs);
|
||||
|
||||
origComponent = KeyPathExpr::Component::
|
||||
forUnresolvedSubscript(ctx, loc, index, {}, loc, loc,
|
||||
/*trailingClosure*/nullptr);
|
||||
cs.setType(origComponent.getIndexExpr(), index->getType());
|
||||
}
|
||||
|
||||
auto resolvedTy = foundDecl->openedType->castTo<AnyFunctionType>()
|
||||
->getResult();
|
||||
|
||||
resolvedTy = simplifyType(resolvedTy);
|
||||
|
||||
auto ref = ConcreteDeclRef(cs.getASTContext(), subscript, subs);
|
||||
@@ -6406,7 +6521,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
|
||||
// coercion.
|
||||
if (auto fromLValue = fromType->getAs<LValueType>()) {
|
||||
if (auto *toIO = toType->getAs<InOutType>()) {
|
||||
// In an 'inout' operator like "++i", the operand is converted from
|
||||
// In an 'inout' operator like "i += 1", the operand is converted from
|
||||
// an implicit lvalue to an inout argument.
|
||||
assert(toIO->getObjectType()->isEqual(fromLValue->getObjectType()));
|
||||
cs.propagateLValueAccessKind(expr, AccessKind::ReadWrite);
|
||||
@@ -8042,7 +8157,8 @@ Expr *TypeChecker::callWitness(Expr *base, DeclContext *dc,
|
||||
}
|
||||
|
||||
Expr *
|
||||
Solution::convertBooleanTypeToBuiltinI1(Expr *expr, ConstraintLocator *locator) const {
|
||||
Solution::convertBooleanTypeToBuiltinI1(Expr *expr,
|
||||
ConstraintLocator *locator) const {
|
||||
auto &cs = getConstraintSystem();
|
||||
|
||||
// Load lvalues here.
|
||||
|
||||
@@ -528,8 +528,9 @@ static bool diagnoseAmbiguity(ConstraintSystem &cs,
|
||||
break;
|
||||
|
||||
case OverloadChoiceKind::KeyPathApplication:
|
||||
// Skip key path applications, since we don't want them to noise up
|
||||
// unrelated subscript diagnostics.
|
||||
case OverloadChoiceKind::DynamicMemberLookup:
|
||||
// Skip key path applications and dynamic member lookups, since we don't
|
||||
// want them to noise up unrelated subscript diagnostics.
|
||||
break;
|
||||
|
||||
case OverloadChoiceKind::BaseType:
|
||||
@@ -3151,9 +3152,17 @@ void ConstraintSystem::diagnoseAssignmentFailure(Expr *dest, Type destTy,
|
||||
diagID = diag::assignment_bang_has_immutable_subcomponent;
|
||||
else if (isa<UnresolvedDotExpr>(dest) || isa<MemberRefExpr>(dest))
|
||||
diagID = diag::assignment_lhs_is_immutable_property;
|
||||
else if (isa<SubscriptExpr>(dest))
|
||||
else if (auto sub = dyn_cast<SubscriptExpr>(dest)) {
|
||||
diagID = diag::assignment_subscript_has_immutable_base;
|
||||
else {
|
||||
|
||||
// If the destination is a subscript with a 'dynamicLookup:' label and if
|
||||
// the tuple is implicit, then this was actually a @dynamicMemberLookup
|
||||
// access. Emit a more specific diagnostic.
|
||||
if (sub->getIndex()->isImplicit() &&
|
||||
sub->getArgumentLabels().size() == 1 &&
|
||||
sub->getArgumentLabels().front() == TC.Context.Id_dynamicMember)
|
||||
diagID = diag::assignment_dynamic_property_has_immutable_base;
|
||||
} else {
|
||||
diagID = diag::assignment_lhs_is_immutable_variable;
|
||||
}
|
||||
|
||||
@@ -4526,7 +4535,8 @@ bool FailureDiagnosis::diagnoseParameterErrors(CalleeCandidateInfo &CCI,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FailureDiagnosis::diagnoseSubscriptErrors(SubscriptExpr *SE, bool inAssignmentDestination) {
|
||||
bool FailureDiagnosis::diagnoseSubscriptErrors(SubscriptExpr *SE,
|
||||
bool inAssignmentDestination) {
|
||||
auto baseExpr = typeCheckChildIndependently(SE->getBase());
|
||||
if (!baseExpr) return true;
|
||||
auto baseType = CS.getType(baseExpr);
|
||||
|
||||
@@ -147,6 +147,7 @@ static bool sameOverloadChoice(const OverloadChoice &x,
|
||||
case OverloadChoiceKind::DeclViaDynamic:
|
||||
case OverloadChoiceKind::DeclViaBridge:
|
||||
case OverloadChoiceKind::DeclViaUnwrappedOptional:
|
||||
case OverloadChoiceKind::DynamicMemberLookup:
|
||||
return sameDecl(x.getDecl(), y.getDecl());
|
||||
|
||||
case OverloadChoiceKind::TupleIndex:
|
||||
@@ -855,6 +856,7 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
|
||||
case OverloadChoiceKind::Decl:
|
||||
case OverloadChoiceKind::DeclViaBridge:
|
||||
case OverloadChoiceKind::DeclViaUnwrappedOptional:
|
||||
case OverloadChoiceKind::DynamicMemberLookup:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -2863,6 +2863,64 @@ getArgumentLabels(ConstraintSystem &cs, ConstraintLocatorBuilder locator) {
|
||||
return known->second;
|
||||
}
|
||||
|
||||
|
||||
/// Return true if the specified type or a super-class/super-protocol has the
|
||||
/// @dynamicMemberLookup attribute on it. This implementation is not
|
||||
/// particularly fast in the face of deep class hierarchies or lots of protocol
|
||||
/// conformances, but this is fine because it doesn't get invoked in the normal
|
||||
/// name lookup path (only when lookup is about to fail).
|
||||
static bool hasDynamicMemberLookupAttribute(Type ty,
|
||||
llvm::DenseMap<Type, bool> &IsDynamicMemberLookupCache) {
|
||||
auto it = IsDynamicMemberLookupCache.find(ty);
|
||||
if (it != IsDynamicMemberLookupCache.end()) return it->second;
|
||||
|
||||
auto calculate = [&]()-> bool {
|
||||
// If this is a protocol composition, check to see if any of the protocols
|
||||
// have the attribute on them.
|
||||
if (auto protocolComp = ty->getAs<ProtocolCompositionType>()) {
|
||||
for (auto p : protocolComp->getMembers())
|
||||
if (hasDynamicMemberLookupAttribute(p, IsDynamicMemberLookupCache))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise this has to be a nominal type.
|
||||
auto nominal = ty->getAnyNominal();
|
||||
if (!nominal) return false; // Dynamic lookups don't exist on tuples, etc.
|
||||
|
||||
// If any of the protocols this type conforms to has the attribute, then
|
||||
// yes.
|
||||
for (auto p : nominal->getAllProtocols())
|
||||
if (p->getAttrs().hasAttribute<DynamicMemberLookupAttr>())
|
||||
return true;
|
||||
|
||||
// Walk superclasses, if present.
|
||||
llvm::SmallPtrSet<const NominalTypeDecl*, 8> visitedDecls;
|
||||
while (1) {
|
||||
// If we found a circular parent class chain, reject this.
|
||||
if (!visitedDecls.insert(nominal).second)
|
||||
return false;
|
||||
|
||||
// If this type has the attribute on it, then yes!
|
||||
if (nominal->getAttrs().hasAttribute<DynamicMemberLookupAttr>())
|
||||
return true;
|
||||
|
||||
// If this is a class with a super class, check super classes as well.
|
||||
if (auto *cd = dyn_cast<ClassDecl>(nominal)) {
|
||||
if (auto superClass = cd->getSuperclassDecl()) {
|
||||
nominal = superClass;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return IsDynamicMemberLookupCache[ty] = calculate();
|
||||
}
|
||||
|
||||
|
||||
/// Given a ValueMember, UnresolvedValueMember, or TypeMember constraint,
|
||||
/// perform a lookup into the specified base type to find a candidate list.
|
||||
/// The list returned includes the viable candidates as well as the unviable
|
||||
@@ -3241,6 +3299,42 @@ retry_after_fail:
|
||||
}
|
||||
}
|
||||
|
||||
// If we're about to fail lookup, but we are looking for members in a type
|
||||
// that has the @dynamicMemberLookup attribute, then we resolve the reference
|
||||
// to the subscript(dynamicMember:) member, and pass the member name as a
|
||||
// string.
|
||||
if (result.ViableCandidates.empty() &&
|
||||
constraintKind == ConstraintKind::ValueMember &&
|
||||
memberName.isSimpleName() && !memberName.isSpecial()) {
|
||||
auto name = memberName.getBaseIdentifier();
|
||||
if (hasDynamicMemberLookupAttribute(instanceTy,
|
||||
IsDynamicMemberLookupCache)) {
|
||||
auto &ctx = getASTContext();
|
||||
// Recursively look up the subscript(dynamicMember:)'s in this type.
|
||||
auto subscriptName =
|
||||
DeclName(ctx, DeclBaseName::createSubscript(), ctx.Id_dynamicMember);
|
||||
|
||||
auto subscripts = performMemberLookup(constraintKind,
|
||||
subscriptName,
|
||||
baseTy, functionRefKind,
|
||||
memberLocator,
|
||||
includeInaccessibleMembers);
|
||||
|
||||
// Reflect the candidates found as DynamicMemberLookup results.
|
||||
for (auto candidate : subscripts.ViableCandidates) {
|
||||
auto decl = cast<SubscriptDecl>(candidate.getDecl());
|
||||
if (isAcceptableDynamicMemberLookupSubscript(decl, DC, TC))
|
||||
result.addViable(OverloadChoice::getDynamicMemberLookup(baseTy,
|
||||
decl, name));
|
||||
}
|
||||
for (auto candidate : subscripts.UnviableCandidates) {
|
||||
auto decl = candidate.first.getDecl();
|
||||
auto choice = OverloadChoice::getDynamicMemberLookup(baseTy, decl,name);
|
||||
result.addUnviable(choice, candidate.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we rejected some possibilities due to an argument-label
|
||||
// mismatch and ended up with nothing, try again ignoring the
|
||||
// labels. This allows us to perform typo correction on the labels.
|
||||
|
||||
@@ -358,6 +358,9 @@ void Constraint::print(llvm::raw_ostream &Out, SourceManager *sm) const {
|
||||
Out << "decl-via-unwrapped-optional ";
|
||||
printDecl();
|
||||
break;
|
||||
case OverloadChoiceKind::DynamicMemberLookup:
|
||||
Out << "dynamic member lookup '" << overload.getName() << "'";
|
||||
break;
|
||||
case OverloadChoiceKind::BaseType:
|
||||
Out << "base type";
|
||||
break;
|
||||
|
||||
@@ -1579,7 +1579,8 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
|
||||
|
||||
case OverloadChoiceKind::DeclViaBridge:
|
||||
case OverloadChoiceKind::DeclViaDynamic:
|
||||
case OverloadChoiceKind::DeclViaUnwrappedOptional: {
|
||||
case OverloadChoiceKind::DeclViaUnwrappedOptional:
|
||||
case OverloadChoiceKind::DynamicMemberLookup: {
|
||||
// Retrieve the type of a reference to the specific declaration choice.
|
||||
if (auto baseTy = choice.getBaseType()) {
|
||||
assert(!baseTy->hasTypeParameter());
|
||||
@@ -1704,6 +1705,32 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
|
||||
increaseScore(SK_Unavailable);
|
||||
}
|
||||
|
||||
if (kind == OverloadChoiceKind::DynamicMemberLookup) {
|
||||
// DynamicMemberLookup results are always a (dynamicMember:T1)->T2
|
||||
// subscript.
|
||||
auto refFnType = refType->castTo<FunctionType>();
|
||||
|
||||
// If this is a dynamic member lookup, then the decl we have is for the
|
||||
// subscript(dynamicMember:) member, but the type we need to return is the
|
||||
// result of the subscript. Dig through it.
|
||||
refType = refFnType->getResult();
|
||||
|
||||
// Before we drop the argument type on the floor, we need to constrain it
|
||||
// to having a literal conformance to ExpressibleByStringLiteral. This
|
||||
// makes the index default to String if otherwise unconstrained.
|
||||
assert(refFnType->getParams().size() == 1 &&
|
||||
"subscript always has one arg");
|
||||
auto argType = refFnType->getParams()[0].getPlainType();
|
||||
|
||||
auto protoKind = KnownProtocolKind::ExpressibleByStringLiteral;
|
||||
auto protocol = getTypeChecker().getProtocol(choice.getDecl()->getLoc(),
|
||||
protoKind);
|
||||
if (!protocol)
|
||||
break;
|
||||
addConstraint(ConstraintKind::LiteralConformsTo, argType,
|
||||
protocol->getDeclaredType(),
|
||||
locator);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1755,6 +1782,7 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
|
||||
|
||||
// Increase the score so that actual subscripts get preference.
|
||||
increaseScore(SK_KeyPathSubscript);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(!refType->hasTypeParameter() && "Cannot have a dependent type here");
|
||||
@@ -1897,13 +1925,16 @@ DeclName OverloadChoice::getName() const {
|
||||
case OverloadChoiceKind::DeclViaUnwrappedOptional:
|
||||
return getDecl()->getFullName();
|
||||
|
||||
case OverloadChoiceKind::KeyPathApplication: {
|
||||
case OverloadChoiceKind::KeyPathApplication:
|
||||
// TODO: This should probably produce subscript(keyPath:), but we
|
||||
// don't currently pre-filter subscript overload sets by argument
|
||||
// keywords, so "subscript" is still the name that keypath subscripts
|
||||
// are looked up by.
|
||||
return DeclBaseName::createSubscript();
|
||||
}
|
||||
|
||||
case OverloadChoiceKind::DynamicMemberLookup:
|
||||
return DeclName(DynamicNameAndFRK.getPointer());
|
||||
|
||||
case OverloadChoiceKind::BaseType:
|
||||
case OverloadChoiceKind::TupleIndex:
|
||||
llvm_unreachable("no name!");
|
||||
|
||||
@@ -1033,6 +1033,10 @@ public:
|
||||
/// The locators of \c Defaultable constraints whose defaults were used.
|
||||
SmallVector<ConstraintLocator *, 8> DefaultedConstraints;
|
||||
|
||||
/// This is a cache that keeps track of whether a given type is known (or not)
|
||||
/// to be a @dynamicMemberLookup type.
|
||||
///
|
||||
llvm::DenseMap<Type, bool> IsDynamicMemberLookupCache;
|
||||
private:
|
||||
/// \brief Describe the candidate expression for partial solving.
|
||||
/// This class used by shrink & solve methods which apply
|
||||
|
||||
@@ -46,6 +46,8 @@ enum class OverloadChoiceKind : int {
|
||||
BaseType,
|
||||
/// \brief The overload choice selects a key path subscripting operation.
|
||||
KeyPathApplication,
|
||||
/// \brief The member is looked up using @dynamicMemberLookup.
|
||||
DynamicMemberLookup,
|
||||
/// \brief The overload choice selects a particular declaration that
|
||||
/// was found by bridging the base value type to its Objective-C
|
||||
/// class type.
|
||||
@@ -86,24 +88,30 @@ class OverloadChoice {
|
||||
typedef llvm::PointerEmbeddedInt<uint32_t, 29>
|
||||
OverloadChoiceKindWithTupleIndex;
|
||||
|
||||
/// \brief Either the declaration pointer or the overload choice kind. The
|
||||
/// second case is represented as an OverloadChoiceKind, but has additional
|
||||
/// values at the top end that represent the tuple index.
|
||||
/// Depending on the OverloadChoiceKind, this could be one of two cases:
|
||||
/// 1) A ValueDecl for the cases that match to a Decl. The exactly kind of
|
||||
/// decl reference is disambiguated with the DeclKind bits in
|
||||
/// BaseAndDeclKind.
|
||||
/// 2) An OverloadChoiceKindWithTupleIndex if this is an overload kind without
|
||||
/// a decl (e.g., a BaseType, keypath, tuple, etc).
|
||||
///
|
||||
llvm::PointerUnion<ValueDecl*, OverloadChoiceKindWithTupleIndex> DeclOrKind;
|
||||
|
||||
/// The kind of function reference.
|
||||
/// FIXME: This needs two bits. Can we pack them somewhere?
|
||||
FunctionRefKind TheFunctionRefKind;
|
||||
/// This holds the kind of function reference (Unapplied, SingleApply,
|
||||
/// DoubleApply, Compound). If this OverloadChoice represents a
|
||||
/// DynamicMemberLookup result, then this holds the identifier for the
|
||||
/// original member being looked up.
|
||||
llvm::PointerIntPair<Identifier, 2, FunctionRefKind> DynamicNameAndFRK;
|
||||
|
||||
public:
|
||||
OverloadChoice()
|
||||
: BaseAndDeclKind(nullptr, 0), DeclOrKind(0),
|
||||
TheFunctionRefKind(FunctionRefKind::Unapplied) {}
|
||||
: BaseAndDeclKind(nullptr, 0), DeclOrKind(),
|
||||
DynamicNameAndFRK(Identifier(), FunctionRefKind::Unapplied) {}
|
||||
|
||||
OverloadChoice(Type base, ValueDecl *value,
|
||||
FunctionRefKind functionRefKind)
|
||||
: BaseAndDeclKind(base, 0),
|
||||
TheFunctionRefKind(functionRefKind) {
|
||||
DynamicNameAndFRK(Identifier(), functionRefKind) {
|
||||
assert(!base || !base->hasTypeParameter());
|
||||
assert((reinterpret_cast<uintptr_t>(value) & (uintptr_t)0x03) == 0 &&
|
||||
"Badly aligned decl");
|
||||
@@ -113,7 +121,7 @@ public:
|
||||
|
||||
OverloadChoice(Type base, OverloadChoiceKind kind)
|
||||
: BaseAndDeclKind(base, 0), DeclOrKind(uint32_t(kind)),
|
||||
TheFunctionRefKind(FunctionRefKind::Unapplied) {
|
||||
DynamicNameAndFRK(Identifier(), FunctionRefKind::Unapplied) {
|
||||
assert(base && "Must have a base type for overload choice");
|
||||
assert(!base->hasTypeParameter());
|
||||
assert(kind != OverloadChoiceKind::Decl &&
|
||||
@@ -126,7 +134,7 @@ public:
|
||||
OverloadChoice(Type base, unsigned index)
|
||||
: BaseAndDeclKind(base, 0),
|
||||
DeclOrKind(uint32_t(OverloadChoiceKind::TupleIndex)+index),
|
||||
TheFunctionRefKind(FunctionRefKind::Unapplied) {
|
||||
DynamicNameAndFRK(Identifier(), FunctionRefKind::Unapplied) {
|
||||
assert(base->getRValueType()->is<TupleType>() && "Must have tuple type");
|
||||
}
|
||||
|
||||
@@ -134,7 +142,7 @@ public:
|
||||
return BaseAndDeclKind.getPointer().isNull() &&
|
||||
BaseAndDeclKind.getInt() == 0 &&
|
||||
DeclOrKind.isNull() &&
|
||||
TheFunctionRefKind == FunctionRefKind::Unapplied;
|
||||
DynamicNameAndFRK.getInt() == FunctionRefKind::Unapplied;
|
||||
}
|
||||
|
||||
/// Retrieve an overload choice for a declaration that was found via
|
||||
@@ -145,7 +153,7 @@ public:
|
||||
result.BaseAndDeclKind.setPointer(base);
|
||||
result.BaseAndDeclKind.setInt(IsDeclViaDynamic);
|
||||
result.DeclOrKind = value;
|
||||
result.TheFunctionRefKind = functionRefKind;
|
||||
result.DynamicNameAndFRK.setInt(functionRefKind);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -157,7 +165,7 @@ public:
|
||||
result.BaseAndDeclKind.setPointer(base);
|
||||
result.BaseAndDeclKind.setInt(IsDeclViaBridge);
|
||||
result.DeclOrKind = value;
|
||||
result.TheFunctionRefKind = functionRefKind;
|
||||
result.DynamicNameAndFRK.setInt(functionRefKind);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -170,7 +178,18 @@ public:
|
||||
result.BaseAndDeclKind.setPointer(base);
|
||||
result.BaseAndDeclKind.setInt(IsDeclViaUnwrappedOptional);
|
||||
result.DeclOrKind = value;
|
||||
result.TheFunctionRefKind = functionRefKind;
|
||||
result.DynamicNameAndFRK.setInt(functionRefKind);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Retrieve an overload choice for a declaration that was found via
|
||||
/// dynamic lookup. The ValueDecl is the subscript(dynamicMember:)
|
||||
static OverloadChoice getDynamicMemberLookup(Type base, ValueDecl *value,
|
||||
Identifier name) {
|
||||
OverloadChoice result;
|
||||
result.BaseAndDeclKind.setPointer(base);
|
||||
result.DeclOrKind = value;
|
||||
result.DynamicNameAndFRK.setPointer(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -181,6 +200,9 @@ public:
|
||||
|
||||
/// \brief Determines the kind of overload choice this is.
|
||||
OverloadChoiceKind getKind() const {
|
||||
if (!DynamicNameAndFRK.getPointer().empty())
|
||||
return OverloadChoiceKind::DynamicMemberLookup;
|
||||
|
||||
if (DeclOrKind.is<ValueDecl*>()) {
|
||||
switch (BaseAndDeclKind.getInt()) {
|
||||
case IsDeclViaBridge: return OverloadChoiceKind::DeclViaBridge;
|
||||
@@ -190,7 +212,6 @@ public:
|
||||
default: return OverloadChoiceKind::Decl;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t kind = DeclOrKind.get<OverloadChoiceKindWithTupleIndex>();
|
||||
if (kind >= (uint32_t)OverloadChoiceKind::TupleIndex)
|
||||
return OverloadChoiceKind::TupleIndex;
|
||||
@@ -232,7 +253,7 @@ public:
|
||||
|
||||
FunctionRefKind getFunctionRefKind() const {
|
||||
assert(isDecl() && "only makes sense for declaration choices");
|
||||
return TheFunctionRefKind;
|
||||
return DynamicNameAndFRK.getInt();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
IGNORED_ATTR(Convenience)
|
||||
IGNORED_ATTR(Effects)
|
||||
IGNORED_ATTR(Exported)
|
||||
IGNORED_ATTR(DynamicMemberLookup)
|
||||
IGNORED_ATTR(FixedLayout)
|
||||
IGNORED_ATTR(Infix)
|
||||
IGNORED_ATTR(Inline)
|
||||
@@ -838,6 +839,8 @@ public:
|
||||
|
||||
void visitCDeclAttr(CDeclAttr *attr);
|
||||
|
||||
void visitDynamicMemberLookupAttr(DynamicMemberLookupAttr *attr);
|
||||
|
||||
void visitFinalAttr(FinalAttr *attr);
|
||||
void visitIBActionAttr(IBActionAttr *attr);
|
||||
void visitNSCopyingAttr(NSCopyingAttr *attr);
|
||||
@@ -918,6 +921,73 @@ static bool isRelaxedIBAction(TypeChecker &TC) {
|
||||
return isiOS(TC) || iswatchOS(TC);
|
||||
}
|
||||
|
||||
/// Given a subscript defined as "subscript(dynamicMember:)->T", return true if
|
||||
/// it is an acceptable implementation of the @dynamicMemberLookup attribute's
|
||||
/// requirement.
|
||||
bool swift::isAcceptableDynamicMemberLookupSubscript(SubscriptDecl *decl,
|
||||
DeclContext *DC,
|
||||
TypeChecker &TC) {
|
||||
// The only thing that we care about is that the index list has exactly one
|
||||
// non-variadic entry. The type must conform to ExpressibleByStringLiteral.
|
||||
auto indices = decl->getIndices();
|
||||
|
||||
auto EBSL =
|
||||
TC.Context.getProtocol(KnownProtocolKind::ExpressibleByStringLiteral);
|
||||
|
||||
return indices->size() == 1 &&
|
||||
!indices->get(0)->isVariadic() &&
|
||||
TC.conformsToProtocol(indices->get(0)->getType(),
|
||||
EBSL, DC, ConformanceCheckOptions());
|
||||
}
|
||||
|
||||
/// The @dynamicMemberLookup attribute is only allowed on types that have at
|
||||
/// least one subscript member declared like this:
|
||||
///
|
||||
/// subscript<KeywordType: ExpressibleByStringLiteral, LookupValue>
|
||||
/// (dynamicMember name: KeywordType) -> LookupValue { get }
|
||||
///
|
||||
/// ... but doesn't care about the mutating'ness of the getter/setter. We just
|
||||
/// manually check the requirements here.
|
||||
///
|
||||
void AttributeChecker::
|
||||
visitDynamicMemberLookupAttr(DynamicMemberLookupAttr *attr) {
|
||||
// This attribute is only allowed on nominal types.
|
||||
auto decl = cast<NominalTypeDecl>(D);
|
||||
auto type = decl->getDeclaredType();
|
||||
|
||||
// Lookup our subscript.
|
||||
auto subscriptName =
|
||||
DeclName(TC.Context, DeclBaseName::createSubscript(),
|
||||
TC.Context.Id_dynamicMember);
|
||||
|
||||
auto lookupOptions = defaultMemberTypeLookupOptions;
|
||||
lookupOptions -= NameLookupFlags::PerformConformanceCheck;
|
||||
|
||||
// Lookup the implementations of our subscript.
|
||||
auto candidates = TC.lookupMember(decl, type, subscriptName, lookupOptions);
|
||||
|
||||
// If we have none, then there is no attribute.
|
||||
if (candidates.empty()) {
|
||||
TC.diagnose(attr->getLocation(), diag::type_invalid_dml, type);
|
||||
attr->setInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// If none of the ones we find are acceptable, then reject one.
|
||||
auto oneCandidate = candidates.front();
|
||||
candidates.filter([&](LookupResultEntry entry)->bool {
|
||||
auto cand = cast<SubscriptDecl>(entry.getValueDecl());
|
||||
return isAcceptableDynamicMemberLookupSubscript(cand, decl, TC);
|
||||
});
|
||||
|
||||
if (candidates.empty()) {
|
||||
TC.diagnose(oneCandidate.getValueDecl()->getLoc(),
|
||||
diag::type_invalid_dml, type);
|
||||
attr->setInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
void AttributeChecker::visitIBActionAttr(IBActionAttr *attr) {
|
||||
// IBActions instance methods must have type Class -> (...) -> ().
|
||||
auto *FD = cast<FuncDecl>(D);
|
||||
|
||||
@@ -3041,6 +3041,12 @@ void Solution::dump(raw_ostream &out) const {
|
||||
<< choice.getBaseType()->getString() << "\n";
|
||||
break;
|
||||
|
||||
case OverloadChoiceKind::DynamicMemberLookup:
|
||||
out << "dynamic member lookup root "
|
||||
<< choice.getBaseType()->getString()
|
||||
<< " name='" << choice.getName() << "'\n";
|
||||
break;
|
||||
|
||||
case OverloadChoiceKind::TupleIndex:
|
||||
out << "tuple " << choice.getBaseType()->getString() << " index "
|
||||
<< choice.getTupleIndex() << "\n";
|
||||
@@ -3224,6 +3230,12 @@ void ConstraintSystem::print(raw_ostream &out) {
|
||||
<< choice.getBaseType()->getString() << "\n";
|
||||
break;
|
||||
|
||||
case OverloadChoiceKind::DynamicMemberLookup:
|
||||
out << "dynamic member lookup:"
|
||||
<< choice.getBaseType()->getString() << " name="
|
||||
<< choice.getName() << "\n";
|
||||
break;
|
||||
|
||||
case OverloadChoiceKind::TupleIndex:
|
||||
out << "tuple " << choice.getBaseType()->getString() << " index "
|
||||
<< choice.getTupleIndex() << "\n";
|
||||
|
||||
@@ -6465,6 +6465,7 @@ public:
|
||||
UNINTERESTING_ATTR(Alignment)
|
||||
UNINTERESTING_ATTR(CDecl)
|
||||
UNINTERESTING_ATTR(Consuming)
|
||||
UNINTERESTING_ATTR(DynamicMemberLookup)
|
||||
UNINTERESTING_ATTR(SILGenName)
|
||||
UNINTERESTING_ATTR(Exported)
|
||||
UNINTERESTING_ATTR(GKInspectable)
|
||||
|
||||
@@ -2553,6 +2553,13 @@ public:
|
||||
const StringRef Message;
|
||||
};
|
||||
|
||||
/// Given a subscript defined as "subscript(dynamicMember:)->T", return true if
|
||||
/// it is an acceptable implementation of the @dynamicMemberLookup attribute's
|
||||
/// requirement.
|
||||
bool isAcceptableDynamicMemberLookupSubscript(SubscriptDecl *decl,
|
||||
DeclContext *DC,
|
||||
TypeChecker &TC);
|
||||
|
||||
} // end namespace swift
|
||||
|
||||
#endif
|
||||
|
||||
@@ -48,9 +48,10 @@ func method(){}
|
||||
@#^KEYWORD3^#
|
||||
class C {}
|
||||
|
||||
// KEYWORD3: Begin completions, 7 items
|
||||
// KEYWORD3: Begin completions, 8 items
|
||||
// KEYWORD3-NEXT: Keyword/None: available[#Class Attribute#]; name=available{{$}}
|
||||
// KEYWORD3-NEXT: Keyword/None: objc[#Class Attribute#]; name=objc{{$}}
|
||||
// KEYWORD3-NEXT: Keyword/None: dynamicMemberLookup[#Class Attribute#]; name=dynamicMemberLookup{{$}}
|
||||
// KEYWORD3-NEXT: Keyword/None: IBDesignable[#Class Attribute#]; name=IBDesignable{{$}}
|
||||
// KEYWORD3-NEXT: Keyword/None: UIApplicationMain[#Class Attribute#]; name=UIApplicationMain{{$}}
|
||||
// KEYWORD3-NEXT: Keyword/None: requires_stored_property_inits[#Class Attribute#]; name=requires_stored_property_inits{{$}}
|
||||
@@ -60,25 +61,28 @@ class C {}
|
||||
|
||||
@#^KEYWORD4^#
|
||||
enum E {}
|
||||
// KEYWORD4: Begin completions, 2 items
|
||||
// KEYWORD4: Begin completions, 3 items
|
||||
// KEYWORD4-NEXT: Keyword/None: available[#Enum Attribute#]; name=available{{$}}
|
||||
// KEYWORD4-NEXT: Keyword/None: objc[#Enum Attribute#]; name=objc{{$}}
|
||||
// KEYWORD4-NEXT: Keyword/None: dynamicMemberLookup[#Enum Attribute#]; name=dynamicMemberLookup
|
||||
// KEYWORD4-NEXT: End completions
|
||||
|
||||
|
||||
@#^KEYWORD5^#
|
||||
struct S{}
|
||||
// KEYWORD5: Begin completions, 1 item
|
||||
// KEYWORD5: Begin completions, 2 item
|
||||
// KEYWORD5-NEXT: Keyword/None: available[#Struct Attribute#]; name=available{{$}}
|
||||
// KEYWORD5-NEXT: Keyword/None: dynamicMemberLookup[#Struct Attribute#]; name=dynamicMemberLookup
|
||||
// KEYWORD5-NEXT: End completions
|
||||
|
||||
|
||||
@#^KEYWORD_LAST^#
|
||||
|
||||
// KEYWORD_LAST: Begin completions, 18 items
|
||||
// KEYWORD_LAST: Begin completions, 19 items
|
||||
// KEYWORD_LAST-NEXT: Keyword/None: available[#Declaration Attribute#]; name=available{{$}}
|
||||
// KEYWORD_LAST-NEXT: Keyword/None: objc[#Declaration Attribute#]; name=objc{{$}}
|
||||
// KEYWORD_LAST-NEXT: Keyword/None: noreturn[#Declaration Attribute#]; name=noreturn{{$}}
|
||||
// KEYWORD_LAST-NEXT: Keyword/None: dynamicMemberLookup[#Declaration Attribute#]; name=dynamicMemberLookup
|
||||
// KEYWORD_LAST-NEXT: Keyword/None: NSCopying[#Declaration Attribute#]; name=NSCopying{{$}}
|
||||
// KEYWORD_LAST-NEXT: Keyword/None: IBAction[#Declaration Attribute#]; name=IBAction{{$}}
|
||||
// KEYWORD_LAST-NEXT: Keyword/None: IBDesignable[#Declaration Attribute#]; name=IBDesignable{{$}}
|
||||
|
||||
392
test/NameBinding/dynamic-member-lookup.swift
Normal file
392
test/NameBinding/dynamic-member-lookup.swift
Normal file
@@ -0,0 +1,392 @@
|
||||
// RUN: %target-swift-frontend -typecheck -verify %s
|
||||
var global = 42
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct Gettable {
|
||||
subscript(dynamicMember member: StaticString) -> Int {
|
||||
return 42
|
||||
}
|
||||
}
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct Settable {
|
||||
subscript(dynamicMember member: StaticString) -> Int {
|
||||
get {return 42}
|
||||
set {}
|
||||
}
|
||||
}
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct MutGettable {
|
||||
subscript(dynamicMember member: StaticString) -> Int {
|
||||
mutating get {
|
||||
return 42
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct NonMutSettable {
|
||||
subscript(dynamicMember member: StaticString) -> Int {
|
||||
get { return 42 }
|
||||
nonmutating set {}
|
||||
}
|
||||
}
|
||||
|
||||
func test_function(b: Settable) {
|
||||
var bm = b
|
||||
bm.flavor = global
|
||||
}
|
||||
|
||||
func test(a: Gettable, b: Settable, c: MutGettable, d: NonMutSettable) {
|
||||
global = a.wyverns
|
||||
a.flavor = global // expected-error {{cannot assign to property: 'a' is a 'let' constant}}
|
||||
|
||||
global = b.flavor
|
||||
b.universal = global // expected-error {{cannot assign to property: 'b' is a 'let' constant}}
|
||||
b.thing += 1 // expected-error {{left side of mutating operator isn't mutable: 'b' is a 'let' constant}}
|
||||
|
||||
var bm = b
|
||||
global = bm.flavor
|
||||
bm.universal = global
|
||||
bm.thing += 1
|
||||
|
||||
var cm = c
|
||||
global = c.dragons // expected-error {{cannot use mutating getter on immutable value: 'c' is a 'let' constant}}
|
||||
global = c[dynamicMember: "dragons"] // expected-error {{cannot use mutating getter on immutable value: 'c' is a 'let' constant}}
|
||||
global = cm.dragons
|
||||
c.woof = global // expected-error {{cannot use mutating getter on immutable value: 'c' is a 'let' constant}}
|
||||
|
||||
var dm = d
|
||||
global = d.dragons // ok
|
||||
global = dm.dragons // ok
|
||||
d.woof = global // ok
|
||||
dm.woof = global // ok
|
||||
}
|
||||
|
||||
|
||||
func test_iuo(a : Gettable!, b : Settable!) {
|
||||
global = a.wyverns
|
||||
a.flavor = global // expected-error {{cannot assign through dynamic lookup property: subscript is get-only}}
|
||||
|
||||
global = b.flavor
|
||||
b.universal = global // expected-error {{cannot assign through dynamic lookup property: 'b' is a 'let' constant}}
|
||||
|
||||
var bm : Settable! = b
|
||||
|
||||
global = bm.flavor
|
||||
bm.universal = global
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Returning a function
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct FnTest {
|
||||
subscript(dynamicMember member: StaticString) -> (_ a : Int)->() {
|
||||
return { a in () }
|
||||
}
|
||||
}
|
||||
func test_function(x : FnTest) {
|
||||
x.phunky(12)
|
||||
}
|
||||
func test_function_iuo(x : FnTest!) {
|
||||
x.flavor(12)
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Existential Cases
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
|
||||
@dynamicMemberLookup
|
||||
protocol ProtoExt { }
|
||||
extension ProtoExt {
|
||||
subscript(dynamicMember member: String) -> String {
|
||||
get {}
|
||||
}
|
||||
}
|
||||
|
||||
extension String: ProtoExt { }
|
||||
|
||||
func testProtoExt() -> String {
|
||||
let str = "test"
|
||||
return str.sdfsdfsdf
|
||||
}
|
||||
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Explicitly declared members take precedence
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct Dog {
|
||||
public var name = "Kaylee"
|
||||
|
||||
subscript(dynamicMember member: String) -> String {
|
||||
return "Zoey"
|
||||
}
|
||||
}
|
||||
|
||||
func testDog(person: Dog) -> String {
|
||||
return person.name + person.otherName
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Returning an IUO
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct IUOResultTest {
|
||||
subscript(dynamicMember member: StaticString) -> Int! {
|
||||
get { return 42 }
|
||||
nonmutating set {}
|
||||
}
|
||||
}
|
||||
|
||||
func test_iuo_result(x : IUOResultTest) {
|
||||
x.foo?.negate() // Test mutating writeback.
|
||||
|
||||
let _ : Int = x.bar // Test implicitly forced optional
|
||||
let b = x.bar // Should promote to 'Int?'
|
||||
let _ : Int = b // expected-error {{value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'}}
|
||||
}
|
||||
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Error cases
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Subscript index must be ExpressibleByStringLiteral.
|
||||
@dynamicMemberLookup
|
||||
struct Invalid1 {
|
||||
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'Invalid1' to have a 'subscript(dynamicMember:)' member with a string index}}
|
||||
subscript(dynamicMember member: Int) -> Int {
|
||||
return 42
|
||||
}
|
||||
}
|
||||
|
||||
// Subscript may not be variadic.
|
||||
@dynamicMemberLookup
|
||||
struct Invalid2 {
|
||||
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'Invalid2' to have a 'subscript(dynamicMember:)' member with a string index}}
|
||||
subscript(dynamicMember member: String...) -> Int {
|
||||
return 42
|
||||
}
|
||||
}
|
||||
|
||||
// References to overloads are resolved just like normal subscript lookup:
|
||||
// they are either contextually disambiguated or are invalid.
|
||||
@dynamicMemberLookup
|
||||
struct Ambiguity {
|
||||
subscript(dynamicMember member: String) -> Int {
|
||||
return 42
|
||||
}
|
||||
subscript(dynamicMember member: String) -> Float {
|
||||
return 42
|
||||
}
|
||||
}
|
||||
|
||||
func testAmbiguity(a : Ambiguity) {
|
||||
let _ : Int = a.flexibility
|
||||
let _ : Float = a.dynamism
|
||||
_ = a.dynamism // expected-error {{ambiguous use of 'subscript(dynamicMember:)'}}
|
||||
}
|
||||
|
||||
|
||||
// expected-error @+1 {{'@dynamicMemberLookup' attribute cannot be applied to this declaration}}
|
||||
@dynamicMemberLookup
|
||||
extension Int {
|
||||
subscript(dynamicMember member: String) -> Int {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
@dynamicMemberLookup // expected-error {{'@dynamicMemberLookup' attribute cannot be applied to this declaration}}
|
||||
func NotAllowedOnFunc() {
|
||||
}
|
||||
|
||||
|
||||
// @dynamicMemberLookup cannot be declared on a base class and fulfilled with a
|
||||
// derived class.
|
||||
|
||||
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'InvalidBase' to have a 'subscript(dynamicMember:)' member with a string index}}
|
||||
@dynamicMemberLookup
|
||||
class InvalidBase {}
|
||||
|
||||
class InvalidDerived : InvalidBase { subscript(dynamicMember: String) -> Int { get {}} }
|
||||
|
||||
// expected-error @+1 {{value of type 'InvalidDerived' has no member 'dynamicallyLookedUp'}}
|
||||
_ = InvalidDerived().dynamicallyLookedUp
|
||||
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Test Existential
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
protocol PyVal {
|
||||
subscript(dynamicMember member: StaticString) -> PyVal { get nonmutating set }
|
||||
}
|
||||
extension PyVal {
|
||||
subscript(dynamicMember member: StaticString) -> PyVal {
|
||||
get { fatalError() } nonmutating set {}
|
||||
}
|
||||
}
|
||||
|
||||
struct MyType : PyVal {
|
||||
}
|
||||
|
||||
|
||||
func testMutableExistential(a : PyVal, b : MyType) -> PyVal {
|
||||
a.x.y = b
|
||||
b.x.y = b
|
||||
return a.foo.bar.baz
|
||||
}
|
||||
|
||||
|
||||
// Verify the protocol compositions and protocol refinements work.
|
||||
protocol SubPyVal : PyVal { }
|
||||
|
||||
typealias ProtocolComp = AnyObject & PyVal
|
||||
|
||||
func testMutableExistential2(a : AnyObject & PyVal, b : SubPyVal,
|
||||
c : ProtocolComp & AnyObject) {
|
||||
a.x.y = b
|
||||
b.x.y = b
|
||||
c.x.y = b
|
||||
}
|
||||
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// JSON example
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
enum JSON {
|
||||
case IntValue(Int)
|
||||
case StringValue(String)
|
||||
case ArrayValue(Array<JSON>)
|
||||
case DictionaryValue(Dictionary<String, JSON>)
|
||||
|
||||
var stringValue : String? {
|
||||
if case .StringValue(let str) = self {
|
||||
return str
|
||||
}
|
||||
return nil
|
||||
}
|
||||
subscript(index: Int) -> JSON? {
|
||||
if case .ArrayValue(let arr) = self {
|
||||
return index < arr.count ? arr[index] : nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
subscript(key: String) -> JSON? {
|
||||
if case .DictionaryValue(let dict) = self {
|
||||
return dict[key]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
subscript(dynamicMember member: String) -> JSON? {
|
||||
if case .DictionaryValue(let dict) = self {
|
||||
return dict[member]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func test_json_example(x : JSON) -> String? {
|
||||
_ = x.name?.first
|
||||
return x.name?.first?.stringValue
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Derived Class Example
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
class BaseClass {
|
||||
subscript(dynamicMember member: String) -> Int {
|
||||
return 42
|
||||
}
|
||||
}
|
||||
class DerivedClass : BaseClass {
|
||||
}
|
||||
|
||||
func testDerivedClass(x : BaseClass, y : DerivedClass) -> Int {
|
||||
return x.life - y.the + x.universe - y.and + x.everything
|
||||
}
|
||||
|
||||
|
||||
// Test that derived classes can add a setter.
|
||||
class DerivedClassWithSetter : BaseClass {
|
||||
override subscript(dynamicMember member: String) -> Int {
|
||||
get { return super[dynamicMember: member] }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
func testOverrideSubscript(a : BaseClass, b: DerivedClassWithSetter) {
|
||||
let x = a.frotz + b.garbalaz
|
||||
b.baranozo = x
|
||||
|
||||
a.balboza = 12 // expected-error {{cannot assign to property}}
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Generics
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct SettableGeneric1<T> {
|
||||
subscript(dynamicMember member: StaticString) -> T? {
|
||||
get {}
|
||||
nonmutating set {}
|
||||
}
|
||||
}
|
||||
|
||||
func testGenericType<T>(a : SettableGeneric1<T>, b : T) -> T? {
|
||||
a.dfasdf = b
|
||||
return a.dfsdffff
|
||||
}
|
||||
|
||||
func testConcreteGenericType(a : SettableGeneric1<Int>) -> Int? {
|
||||
a.dfasdf = 42
|
||||
return a.dfsdffff
|
||||
}
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct SettableGeneric2<T> {
|
||||
subscript<U: ExpressibleByStringLiteral>(dynamicMember member: U) -> T {
|
||||
get {}
|
||||
nonmutating set {}
|
||||
}
|
||||
}
|
||||
|
||||
func testGenericType2<T>(a : SettableGeneric2<T>, b : T) -> T? {
|
||||
a[dynamicMember: "fasdf"] = b
|
||||
a.dfasdf = b
|
||||
return a.dfsdffff
|
||||
}
|
||||
|
||||
func testConcreteGenericType2(a : SettableGeneric2<Int>) -> Int? {
|
||||
a.dfasdf = 42
|
||||
return a.dfsdffff
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Keypaths
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@dynamicMemberLookup
|
||||
class C {
|
||||
subscript(dynamicMember member: String) -> Int { return 7 }
|
||||
}
|
||||
_ = \C.[dynamicMember: "hi"]
|
||||
_ = \C.testLookup
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user