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:
Chris Lattner
2018-02-16 16:19:50 -08:00
committed by GitHub
parent 3184dd8ad4
commit a0fa5d11b4
17 changed files with 845 additions and 71 deletions

View File

@@ -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)

View File

@@ -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))

View File

@@ -44,6 +44,7 @@ IDENTIFIER(decode)
IDENTIFIER(decodeIfPresent)
IDENTIFIER(Decoder)
IDENTIFIER(decoder)
IDENTIFIER(dynamicMember)
IDENTIFIER(Element)
IDENTIFIER(Encodable)
IDENTIFIER(encode)

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -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!");

View File

@@ -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

View File

@@ -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();
}
};

View File

@@ -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);

View File

@@ -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";

View File

@@ -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)

View File

@@ -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

View File

@@ -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{{$}}

View 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