Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SE-0195, which introduces "Dynamic Member Lookup" Types #14546

Merged
merged 14 commits into from
Feb 17, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -2853,6 +2856,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))

Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ IDENTIFIER(decode)
IDENTIFIER(decodeIfPresent)
IDENTIFIER(Decoder)
IDENTIFIER(decoder)
IDENTIFIER(dynamicMember)
IDENTIFIER(Element)
IDENTIFIER(Encodable)
IDENTIFIER(encode)
Expand Down
129 changes: 98 additions & 31 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1370,13 +1370,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
Expand Down Expand Up @@ -1418,10 +1420,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;
}
Expand Down Expand Up @@ -1528,30 +1536,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);
};
Expand All @@ -1562,7 +1585,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);

Expand Down Expand Up @@ -2738,23 +2761,66 @@ 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,
selected.choice.getTupleIndex(),
nameLoc.getBaseNameLoc(), toType));
}

case OverloadChoiceKind::BaseType: {
case OverloadChoiceKind::BaseType:
return base;
}

case OverloadChoiceKind::KeyPathApplication:
llvm_unreachable("should only happen in a subscript");
}

llvm_unreachable("Unhandled OverloadChoiceKind in switch.");

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

// Build and type check the string literal index value to the specific
// string type expected by the subscript.
Expr *nameExpr = new (ctx)
StringLiteralExpr(selected.choice.getName().getBaseIdentifier().str(),
loc, /*implicit*/true);

// 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();

// Build a tuple so that argument has a label.
Expr *tuple = TupleExpr::create(cs.getASTContext(), loc, nameExpr,
ctx.Id_dynamicMember, loc,
nameLoc.getStartLoc(),
/*hasTrailingClosure*/false,
/*implicit*/true);
auto tupleTy = TupleType::get(TupleTypeElt(stringType,
ctx.Id_dynamicMember), ctx);
(void)cs.TC.typeCheckExpression(tuple, dc,
TypeLoc::withoutLoc(tupleTy),
CTP_CallArgument);
cs.cacheExprTypes(tuple);

// Build and return a subscript that uses this string as the index.
return buildSubscript(base, tuple, ctx.Id_dynamicMember,
/*trailingClosure*/false,
cs.getConstraintLocator(expr),
/*isImplicit*/false,
AccessSemantics::Ordinary, selected);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to run into issues with IUOs here. In resolveOverload() you end up calling into buildDisjunctionForImplicitlyUnwrappedOptional with a locator for Members, but here buildSubscript is going to try to look up the disjunction choice for a SubscriptMember.

It doesn't look like you have tests with IUO subscript results, so it's worth adding one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, looks like a problem with IUO results, great catch, I'll fix. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. IUO problem fixed and testcase added!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

}
}

llvm_unreachable("Unhandled OverloadChoiceKind in switch.");
}

public:
Expand Down Expand Up @@ -6403,7 +6469,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);
Expand Down Expand Up @@ -8038,7 +8104,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.
Expand Down
20 changes: 15 additions & 5 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -3149,9 +3150,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;
}

Expand Down Expand Up @@ -4524,7 +4533,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);
Expand Down
2 changes: 2 additions & 0 deletions lib/Sema/CSRanking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -855,6 +856,7 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
case OverloadChoiceKind::Decl:
case OverloadChoiceKind::DeclViaBridge:
case OverloadChoiceKind::DeclViaUnwrappedOptional:
case OverloadChoiceKind::DynamicMemberLookup:
break;
}

Expand Down
Loading