Skip to content

Commit a0fa5d1

Browse files
authored
Implement SE-0195, which introduces "Dynamic Member Lookup" Types (swiftlang#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!
1 parent 3184dd8 commit a0fa5d1

17 files changed

+848
-74
lines changed

include/swift/AST/Attr.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ SIMPLE_DECL_ATTR(noreturn, NoReturn, OnFunc, 7)
111111

112112
SIMPLE_DECL_ATTR(_exported, Exported, OnImport | UserInaccessible, 8)
113113

114-
/// NOTE: 9 is unused.
114+
SIMPLE_DECL_ATTR(dynamicMemberLookup, DynamicMemberLookup,
115+
OnClass | OnStruct | OnEnum | OnProtocol, 9)
115116

116117
SIMPLE_DECL_ATTR(NSCopying, NSCopying,
117118
OnVar, 10)

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,9 @@ NOTE(archetype_declared_in_type,none,
962962
NOTE(unbound_generic_parameter_explicit_fix,none,
963963
"explicitly specify the generic arguments to fix this issue", ())
964964

965+
ERROR(type_invalid_dml,none,
966+
"@dynamicMemberLookup attribute requires %0 to have a "
967+
"'subscript(dynamicMember:)' member with a string index", (Type))
965968

966969
ERROR(string_index_not_integer,none,
967970
"String must not be indexed with %0, it has variable size elements",
@@ -2857,6 +2860,8 @@ ERROR(assignment_lhs_is_immutable_property,none,
28572860
"cannot assign to property: %0", (StringRef))
28582861
ERROR(assignment_subscript_has_immutable_base,none,
28592862
"cannot assign through subscript: %0", (StringRef))
2863+
ERROR(assignment_dynamic_property_has_immutable_base,none,
2864+
"cannot assign through dynamic lookup property: %0", (StringRef))
28602865
ERROR(assignment_bang_has_immutable_subcomponent,none,
28612866
"cannot assign through '!': %0", (StringRef))
28622867

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ IDENTIFIER(decode)
4444
IDENTIFIER(decodeIfPresent)
4545
IDENTIFIER(Decoder)
4646
IDENTIFIER(decoder)
47+
IDENTIFIER(dynamicMember)
4748
IDENTIFIER(Element)
4849
IDENTIFIER(Encodable)
4950
IDENTIFIER(encode)

lib/Sema/CSApply.cpp

Lines changed: 159 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,29 @@ diagnoseInvalidDynamicConstructorReferences(ConstraintSystem &cs,
401401
return true;
402402
}
403403

404+
/// Form a type checked expression for the index of a @dynamicMemberLookup
405+
/// subscript index expression. This will have tuple type of (dynamicMember:T).
406+
static Expr *getDMLIndexExpr(StringRef name, Type ty, SourceLoc loc,
407+
DeclContext *dc, ConstraintSystem &cs) {
408+
auto &ctx = cs.TC.Context;
409+
410+
// Build and type check the string literal index value to the specific
411+
// string type expected by the subscript.
412+
Expr *nameExpr = new (ctx)
413+
StringLiteralExpr(name, loc, /*implicit*/true);
414+
415+
416+
// Build a tuple so that argument has a label.
417+
Expr *tuple = TupleExpr::create(ctx, loc, nameExpr, ctx.Id_dynamicMember, loc,
418+
loc, /*hasTrailingClosure*/false,
419+
/*implicit*/true);
420+
(void)cs.TC.typeCheckExpression(tuple, dc, TypeLoc::withoutLoc(ty),
421+
CTP_CallArgument);
422+
cs.cacheExprTypes(tuple);
423+
return tuple;
424+
}
425+
426+
404427
namespace {
405428

406429
/// \brief Rewrites an expression by applying the solution of a constraint
@@ -1370,13 +1393,15 @@ namespace {
13701393
ArrayRef<Identifier> argLabels,
13711394
bool hasTrailingClosure,
13721395
ConstraintLocatorBuilder locator, bool isImplicit,
1373-
AccessSemantics semantics) {
1396+
AccessSemantics semantics,
1397+
Optional<SelectedOverload> selected = None) {
13741398

13751399
// Determine the declaration selected for this subscript operation.
1376-
auto selected = getOverloadChoiceIfAvailable(
1377-
cs.getConstraintLocator(
1378-
locator.withPathElement(
1379-
ConstraintLocator::SubscriptMember)));
1400+
if (!selected)
1401+
selected = getOverloadChoiceIfAvailable(
1402+
cs.getConstraintLocator(
1403+
locator.withPathElement(
1404+
ConstraintLocator::SubscriptMember)));
13801405

13811406
// Handles situation where there was a solution available but it didn't
13821407
// have a proper overload selected from subscript call, might be because
@@ -1418,10 +1443,16 @@ namespace {
14181443
}
14191444
}
14201445

1421-
if (selected->choice.isDecl())
1446+
if (selected->choice.isDecl()) {
1447+
auto locatorKind = ConstraintLocator::SubscriptMember;
1448+
if (selected->choice.getKind() ==
1449+
OverloadChoiceKind::DynamicMemberLookup)
1450+
locatorKind = ConstraintLocator::Member;
1451+
14221452
newSubscript = forceUnwrapIfExpected(
14231453
newSubscript, selected->choice.getDecl(),
1424-
locator.withPathElement(ConstraintLocator::SubscriptMember));
1454+
locator.withPathElement(locatorKind));
1455+
}
14251456

14261457
return newSubscript;
14271458
}
@@ -1528,30 +1559,45 @@ namespace {
15281559
// Check whether the base is 'super'.
15291560
bool isSuper = base->isSuperExpr();
15301561

1531-
// Figure out the index and result types.
1532-
auto subscriptTy = simplifyType(selected.openedType);
1533-
auto subscriptFnTy = subscriptTy->castTo<AnyFunctionType>();
1534-
auto resultTy = subscriptFnTy->getResult();
1535-
1562+
// Use the correct kind of locator depending on how this subscript came
1563+
// to be.
1564+
auto locatorKind = ConstraintLocator::SubscriptMember;
1565+
if (choice.getKind() == OverloadChoiceKind::DynamicMemberLookup)
1566+
locatorKind = ConstraintLocator::Member;
1567+
15361568
// If we opened up an existential when performing the subscript, open
15371569
// the base accordingly.
15381570
auto knownOpened = solution.OpenedExistentialTypes.find(
15391571
getConstraintSystem().getConstraintLocator(
1540-
locator.withPathElement(
1541-
ConstraintLocator::SubscriptMember)));
1572+
locator.withPathElement(locatorKind)));
15421573
if (knownOpened != solution.OpenedExistentialTypes.end()) {
15431574
base = openExistentialReference(base, knownOpened->second, subscript);
15441575
baseTy = knownOpened->second;
15451576
}
1577+
1578+
// Figure out the index and result types.
1579+
Type resultTy;
1580+
if (choice.getKind() != OverloadChoiceKind::DynamicMemberLookup) {
1581+
auto subscriptTy = simplifyType(selected.openedType);
1582+
auto *subscriptFnTy = subscriptTy->castTo<FunctionType>();
1583+
resultTy = subscriptFnTy->getResult();
1584+
1585+
// Coerce the index argument.
1586+
index = coerceCallArguments(index, subscriptFnTy, nullptr,
1587+
argLabels, hasTrailingClosure,
1588+
locator.withPathElement(
1589+
ConstraintLocator::SubscriptIndex));
1590+
if (!index)
1591+
return nullptr;
15461592

1547-
// Coerce the index argument.
1548-
index = coerceCallArguments(index, subscriptFnTy, nullptr,
1549-
argLabels, hasTrailingClosure,
1550-
locator.withPathElement(
1551-
ConstraintLocator::SubscriptIndex));
1552-
if (!index)
1553-
return nullptr;
1554-
1593+
} else {
1594+
// If this is a @dynamicMemberLookup, then the type of the selection is
1595+
// actually the property/result type. That's fine though, and we
1596+
// already have the index type adjusted to the correct type expected by
1597+
// the subscript.
1598+
resultTy = simplifyType(selected.openedType);
1599+
}
1600+
15551601
auto getType = [&](const Expr *E) -> Type {
15561602
return cs.getType(E);
15571603
};
@@ -1562,7 +1608,7 @@ namespace {
15621608
SmallVector<Substitution, 4> substitutions;
15631609
solution.computeSubstitutions(
15641610
subscript->getInnermostDeclContext()->getGenericSignatureOfContext(),
1565-
locator.withPathElement(ConstraintLocator::SubscriptMember),
1611+
locator.withPathElement(locatorKind),
15661612
substitutions);
15671613
ConcreteDeclRef subscriptRef(tc.Context, subscript, substitutions);
15681614

@@ -2737,23 +2783,54 @@ namespace {
27372783
// before taking a single element.
27382784
auto baseTy = cs.getType(base);
27392785
if (!toType->hasLValueType() && baseTy->hasLValueType())
2740-
base = coerceToType(base, baseTy->getRValueType(), cs.getConstraintLocator(base));
2786+
base = coerceToType(base, baseTy->getRValueType(),
2787+
cs.getConstraintLocator(base));
27412788

27422789
return cs.cacheType(new (cs.getASTContext())
27432790
TupleElementExpr(base, dotLoc,
27442791
selected.choice.getTupleIndex(),
27452792
nameLoc.getBaseNameLoc(), toType));
27462793
}
27472794

2748-
case OverloadChoiceKind::BaseType: {
2795+
case OverloadChoiceKind::BaseType:
27492796
return base;
2750-
}
27512797

27522798
case OverloadChoiceKind::KeyPathApplication:
27532799
llvm_unreachable("should only happen in a subscript");
2754-
}
2755-
2756-
llvm_unreachable("Unhandled OverloadChoiceKind in switch.");
2800+
2801+
case OverloadChoiceKind::DynamicMemberLookup: {
2802+
// Application of a DynamicMemberLookup result turns a member access of
2803+
// x.foo into x[dynamicMember: "foo"].
2804+
auto &ctx = cs.getASTContext();
2805+
auto loc = nameLoc.getStartLoc();
2806+
2807+
// Figure out the expected type of the string. We know the
2808+
// openedFullType will be "xType -> indexType -> resultType". Dig out
2809+
// its index type.
2810+
auto declTy = solution.simplifyType(selected.openedFullType);
2811+
auto subscriptTy = declTy->castTo<FunctionType>()->getResult();
2812+
auto refFnType = subscriptTy->castTo<FunctionType>();
2813+
assert(refFnType->getParams().size() == 1 &&
2814+
"subscript always has one arg");
2815+
auto stringType = refFnType->getParams()[0].getPlainType();
2816+
auto tupleTy = TupleType::get(TupleTypeElt(stringType,
2817+
ctx.Id_dynamicMember), ctx);
2818+
2819+
// Build and type check the string literal index value to the specific
2820+
// string type expected by the subscript.
2821+
auto fieldName = selected.choice.getName().getBaseIdentifier().str();
2822+
auto index = getDMLIndexExpr(fieldName, tupleTy, loc, dc, cs);
2823+
2824+
// Build and return a subscript that uses this string as the index.
2825+
return buildSubscript(base, index, ctx.Id_dynamicMember,
2826+
/*trailingClosure*/false,
2827+
cs.getConstraintLocator(expr),
2828+
/*isImplicit*/false,
2829+
AccessSemantics::Ordinary, selected);
2830+
}
2831+
}
2832+
2833+
llvm_unreachable("Unhandled OverloadChoiceKind in switch.");
27572834
}
27582835

27592836
public:
@@ -4154,7 +4231,7 @@ namespace {
41544231
Type leafTy = keyPathTy->getGenericArgs()[1];
41554232

41564233
for (unsigned i : indices(E->getComponents())) {
4157-
auto &origComponent = E->getComponents()[i];
4234+
auto &origComponent = E->getMutableComponents()[i];
41584235

41594236
// If there were unresolved types, we may end up with a null base for
41604237
// following components.
@@ -4181,17 +4258,36 @@ namespace {
41814258
return objectTy;
41824259
};
41834260

4184-
KeyPathExpr::Component component;
4185-
switch (auto kind = origComponent.getKind()) {
4186-
case KeyPathExpr::Component::Kind::UnresolvedProperty: {
4187-
auto locator = cs.getConstraintLocator(E,
4261+
auto kind = origComponent.getKind();
4262+
Optional<SelectedOverload> foundDecl;
4263+
4264+
auto locator = cs.getConstraintLocator(E,
41884265
ConstraintLocator::PathElement::getKeyPathComponent(i));
4189-
auto foundDecl = getOverloadChoiceIfAvailable(locator);
4266+
4267+
// If this is an unresolved link, make sure we resolved it.
4268+
if (kind == KeyPathExpr::Component::Kind::UnresolvedProperty ||
4269+
kind == KeyPathExpr::Component::Kind::UnresolvedSubscript) {
4270+
foundDecl = getOverloadChoiceIfAvailable(locator);
41904271
// Leave the component unresolved if the overload was not resolved.
4272+
if (foundDecl) {
4273+
// If this was a @dynamicMemberLookup property, then we actually
4274+
// form a subscript reference, so switch the kind.
4275+
if (foundDecl->choice.getKind()
4276+
== OverloadChoiceKind::DynamicMemberLookup) {
4277+
kind = KeyPathExpr::Component::Kind::UnresolvedSubscript;
4278+
}
4279+
}
4280+
}
4281+
4282+
KeyPathExpr::Component component;
4283+
switch (kind) {
4284+
case KeyPathExpr::Component::Kind::UnresolvedProperty: {
4285+
// If we couldn't resolve the component, leave it alone.
41914286
if (!foundDecl) {
41924287
component = origComponent;
41934288
break;
41944289
}
4290+
41954291
auto property = foundDecl->choice.getDecl();
41964292

41974293
// Key paths can only refer to properties currently.
@@ -4231,6 +4327,7 @@ namespace {
42314327
resolvedTy = simplifyType(resolvedTy);
42324328

42334329
auto ref = ConcreteDeclRef(cs.getASTContext(), property, subs);
4330+
42344331
component = KeyPathExpr::Component::forProperty(ref,
42354332
resolvedTy,
42364333
origComponent.getLoc());
@@ -4248,14 +4345,12 @@ namespace {
42484345
break;
42494346
}
42504347
case KeyPathExpr::Component::Kind::UnresolvedSubscript: {
4251-
auto locator = cs.getConstraintLocator(E,
4252-
ConstraintLocator::PathElement::getKeyPathComponent(i));
4253-
auto foundDecl = getOverloadChoiceIfAvailable(locator);
42544348
// Leave the component unresolved if the overload was not resolved.
42554349
if (!foundDecl) {
42564350
component = origComponent;
42574351
break;
42584352
}
4353+
42594354
auto subscript = cast<SubscriptDecl>(foundDecl->choice.getDecl());
42604355
if (subscript->isGetterMutating()) {
42614356
cs.TC.diagnose(origComponent.getLoc(),
@@ -4267,18 +4362,38 @@ namespace {
42674362

42684363
auto dc = subscript->getInnermostDeclContext();
42694364
SmallVector<Substitution, 4> subs;
4270-
SubstitutionMap subMap;
42714365
auto indexType = subscript->getIndicesInterfaceType();
42724366

42734367
if (auto sig = dc->getGenericSignatureOfContext()) {
42744368
// Compute substitutions to refer to the member.
42754369
solution.computeSubstitutions(sig, locator, subs);
4276-
subMap = sig->getSubstitutionMap(subs);
4277-
indexType = indexType.subst(subMap);
4370+
indexType = indexType.subst(sig->getSubstitutionMap(subs));
4371+
}
4372+
4373+
// If this is a @dynamicMemberLookup reference to resolve a property
4374+
// through the subscript(dynamicMember:) member, restore the
4375+
// openedType and origComponent to its full reference as if the user
4376+
// wrote out the subscript manually.
4377+
if (foundDecl->choice.getKind()
4378+
== OverloadChoiceKind::DynamicMemberLookup) {
4379+
foundDecl->openedType = foundDecl->openedFullType
4380+
->castTo<AnyFunctionType>()->getResult();
4381+
4382+
auto &ctx = cs.TC.Context;
4383+
auto loc = origComponent.getLoc();
4384+
auto fieldName =
4385+
foundDecl->choice.getName().getBaseIdentifier().str();
4386+
auto index = getDMLIndexExpr(fieldName, indexType, loc, dc, cs);
4387+
4388+
origComponent = KeyPathExpr::Component::
4389+
forUnresolvedSubscript(ctx, loc, index, {}, loc, loc,
4390+
/*trailingClosure*/nullptr);
4391+
cs.setType(origComponent.getIndexExpr(), index->getType());
42784392
}
42794393

42804394
auto resolvedTy = foundDecl->openedType->castTo<AnyFunctionType>()
42814395
->getResult();
4396+
42824397
resolvedTy = simplifyType(resolvedTy);
42834398

42844399
auto ref = ConcreteDeclRef(cs.getASTContext(), subscript, subs);
@@ -6406,7 +6521,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
64066521
// coercion.
64076522
if (auto fromLValue = fromType->getAs<LValueType>()) {
64086523
if (auto *toIO = toType->getAs<InOutType>()) {
6409-
// In an 'inout' operator like "++i", the operand is converted from
6524+
// In an 'inout' operator like "i += 1", the operand is converted from
64106525
// an implicit lvalue to an inout argument.
64116526
assert(toIO->getObjectType()->isEqual(fromLValue->getObjectType()));
64126527
cs.propagateLValueAccessKind(expr, AccessKind::ReadWrite);
@@ -8042,7 +8157,8 @@ Expr *TypeChecker::callWitness(Expr *base, DeclContext *dc,
80428157
}
80438158

80448159
Expr *
8045-
Solution::convertBooleanTypeToBuiltinI1(Expr *expr, ConstraintLocator *locator) const {
8160+
Solution::convertBooleanTypeToBuiltinI1(Expr *expr,
8161+
ConstraintLocator *locator) const {
80468162
auto &cs = getConstraintSystem();
80478163

80488164
// Load lvalues here.

0 commit comments

Comments
 (0)