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

@@ -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,13 +1393,15 @@ 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(
cs.getConstraintLocator(
locator.withPathElement(
ConstraintLocator::SubscriptMember)));
if (!selected)
selected = getOverloadChoiceIfAvailable(
cs.getConstraintLocator(
locator.withPathElement(
ConstraintLocator::SubscriptMember)));
// Handles situation where there was a solution available but it didn't
// have a proper overload selected from subscript call, might be because
@@ -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,30 +1559,45 @@ 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,
locator.withPathElement(
ConstraintLocator::SubscriptIndex));
if (!index)
return nullptr;
// Coerce the index argument.
index = coerceCallArguments(index, subscriptFnTy, nullptr,
argLabels, hasTrailingClosure,
locator.withPathElement(
ConstraintLocator::SubscriptIndex));
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,15 +2792,45 @@ 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.");
llvm_unreachable("Unhandled OverloadChoiceKind in switch.");
}
public:
@@ -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 locator = cs.getConstraintLocator(E,
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.