Skip to content

Commit

Permalink
Implement implicit member chains
Browse files Browse the repository at this point in the history
  • Loading branch information
Jumhyn committed Aug 27, 2020
1 parent 9ee8cd2 commit 5b5e30b
Show file tree
Hide file tree
Showing 14 changed files with 509 additions and 100 deletions.
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ ERROR(expected_argument_in_contextual_member,none,
"member %0 expects argument of type %1", (DeclName, Type))
ERROR(expected_parens_in_contextual_member,none,
"member %0 is a function; did you mean to call it?", (DeclName))
ERROR(expected_parens_in_contextual_member_type,none,
"member %0 is a function that produces expected type %1; did you mean to call it?", (DeclName, Type))

ERROR(expected_result_in_contextual_member,none,
"member %0 in %2 produces result of type %1, but context expects %2",
Expand Down
23 changes: 16 additions & 7 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2753,17 +2753,16 @@ namespace {
}

Expr *visitUnresolvedMemberExpr(UnresolvedMemberExpr *expr) {
// Dig out the type of the base, which will be the result type of this
// expression. If constraint solving resolved this to an UnresolvedType,
// If constraint solving resolved the base type to an UnresolvedType,
// then we're in an ambiguity tolerant mode used for diagnostic
// generation. Just leave this as an unresolved member reference.
Type resultTy = simplifyType(cs.getType(expr));
if (resultTy->hasUnresolvedType()) {
cs.setType(expr, resultTy);
Type baseTy = simplifyType(solution.getUnresolvedMemberBaseType(expr));

if (baseTy->hasUnresolvedType()) {
cs.setType(expr, baseTy);
return expr;
}

Type baseTy = resultTy->getRValueType();
auto &ctx = cs.getASTContext();

// Find the selected member.
Expand Down Expand Up @@ -2821,7 +2820,8 @@ namespace {
diagnoseAmbiguousNominalMember(baseTy, result);
}

return coerceToType(result, resultTy, cs.getConstraintLocator(expr));
return coerceToType(result, simplifyType(cs.getType(expr)),
cs.getConstraintLocator(expr));
}

/// Diagnose if the base type is optional, we're referring to a nominal
Expand Down Expand Up @@ -8449,6 +8449,15 @@ void Solution::setExprTypes(Expr *expr) const {
expr->walk(SET);
}

Type Solution::getUnresolvedMemberBaseType(UnresolvedMemberExpr *expr) const {
auto result = unresolvedMemberBaseTypes.find(expr);
if (result != unresolvedMemberBaseTypes.end())
return result->second;

auto &cs = getConstraintSystem();
return cs.getUnresolvedMemberBaseType(expr);
}

/// MARK: SolutionResult implementation.

SolutionResult SolutionResult::forSolved(Solution &&solution) {
Expand Down
45 changes: 27 additions & 18 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1200,14 +1200,6 @@ bool MissingOptionalUnwrapFailure::diagnoseAsError() {

auto *anchor = castToExpr(getAnchor());

// If this is an unresolved member expr e.g. `.foo` its
// base type is going to be the same as result type minus
// r-value adjustment because base could be an l-value type.
// We want to fix both cases by only diagnose one of them,
// otherwise this is just going to result in a duplcate diagnostic.
if (getLocator()->isLastElement<LocatorPathElt::UnresolvedMember>())
return false;

if (auto assignExpr = dyn_cast<AssignExpr>(anchor))
anchor = assignExpr->getSrc();

Expand Down Expand Up @@ -2137,10 +2129,15 @@ bool ContextualFailure::diagnoseAsError() {
return false;
}

case ConstraintLocator::RValueAdjustment: {
case ConstraintLocator::Member:
case ConstraintLocator::FunctionResult:
case ConstraintLocator::UnresolvedMember: {
auto &solution = getSolution();
auto overload = getOverloadChoiceIfAvailable(
getConstraintLocator(anchor, ConstraintLocator::UnresolvedMember));
auto locator = getConstraintLocator(anchor,
isExpr<UnresolvedMemberExpr>(anchor)
? ConstraintLocator::UnresolvedMember
: ConstraintLocator::Member);
auto overload = getOverloadChoiceIfAvailable(locator);
if (!(overload && overload->choice.isDecl()))
return false;

Expand Down Expand Up @@ -2175,13 +2172,22 @@ bool ContextualFailure::diagnoseAsError() {
});

if (numMissingArgs == 0 || numMissingArgs > 1) {
auto diagnostic = emitDiagnostic(
diag::expected_parens_in_contextual_member, choice->getName());

// If there are no parameters we can suggest a fix-it
// to form an explicit call.
if (numMissingArgs == 0)
diagnostic.fixItInsertAfter(getSourceRange().End, "()");
auto applyFixIt = [&](InFlightDiagnostic &diagnostic) {
// If there are no parameters we can suggest a fix-it
// to form an explicit call.
if (numMissingArgs == 0)
diagnostic.fixItInsertAfter(getSourceRange().End, "()");
};
if (fnType->getResult()->isEqual(toType)) {
auto diag = emitDiagnostic(
diag::expected_parens_in_contextual_member_type,
choice->getName(), fnType->getResult());
applyFixIt(diag);
} else {
auto diag = emitDiagnostic(diag::expected_parens_in_contextual_member,
choice->getName());
applyFixIt(diag);
}
} else {
emitDiagnostic(diag::expected_argument_in_contextual_member,
choice->getName(), params.front().getPlainType());
Expand Down Expand Up @@ -2400,6 +2406,9 @@ bool ContextualFailure::diagnoseMissingFunctionCall() const {
if (getLocator()->isLastElement<LocatorPathElt::RValueAdjustment>())
return false;

if (getLocator()->isLastElement<LocatorPathElt::UnresolvedMember>())
return false;

auto *srcFT = getFromType()->getAs<FunctionType>();
if (!srcFT ||
!(srcFT->getParams().empty() ||
Expand Down
115 changes: 77 additions & 38 deletions lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,35 @@ namespace {
return tv;
}

Type addUnresolvedMemberChainConstraints(Expr *expr, Type resultTy,
ConstraintLocator *resultLocator,
unsigned additionalOptions = 0) {
// If this is a member chain hanging off of an UnresolvedMemberExpr,
// and we're at the last element of the chain, then the contextual type
// (represented with a new type variable) must equal the base type.
if (CS.isMemberChainTail(expr)) {
auto *chainBaseExpr = CS.getMemberChainBase(expr);
if (auto *UME = dyn_cast<UnresolvedMemberExpr>(chainBaseExpr)) {
// Create a new type variable representing the result of the chain.
auto chainResultTy = CS.createTypeVariable(resultLocator,
additionalOptions | TVO_CanBindToHole | TVO_CanBindToNoEscape);
auto chainBaseTy = CS.getUnresolvedMemberBaseType(UME);

// The result of this element of the chain must be convertible to the
// contextual type, and the contextual type must be equal to the base.
CS.addConstraint(ConstraintKind::Conversion, resultTy, chainResultTy,
resultLocator);
CS.addConstraint(ConstraintKind::Equal, chainBaseTy, chainResultTy,
CS.getConstraintLocator(UME, ConstraintLocator::MemberRefBase));

return chainResultTy;
}
}

// Otherwise, just use the proposed result type.
return resultTy;
}

/// Add constraints for a subscript operation.
Type addSubscriptConstraints(
Expr *anchor, Type baseTy, Expr *index,
Expand Down Expand Up @@ -1431,15 +1460,17 @@ namespace {
expr->getArgument() ? FunctionRefKind::DoubleApply
: FunctionRefKind::Compound;

auto memberLocator
= CS.getConstraintLocator(expr, ConstraintLocator::UnresolvedMember);
auto memberLocator = CS.getConstraintLocator(expr,
ConstraintLocator::UnresolvedMember);

// Since base type in this case is completely dependent on context it
// should be marked as a potential hole.
auto baseTy = CS.createTypeVariable(baseLocator, TVO_CanBindToNoEscape |
TVO_CanBindToHole);
auto memberTy = CS.createTypeVariable(
memberLocator, TVO_CanBindToLValue | TVO_CanBindToNoEscape);
auto baseTy = CS.createTypeVariable(baseLocator,
TVO_CanBindToHole | TVO_CanBindToNoEscape);
CS.setUnresolvedMemberBaseType(expr, baseTy);

auto memberTy = CS.createTypeVariable(memberLocator,
TVO_CanBindToLValue | TVO_CanBindToNoEscape);

// An unresolved member expression '.member' is modeled as a value member
// constraint
Expand All @@ -1455,14 +1486,11 @@ namespace {

// If there is an argument, apply it.
if (auto arg = expr->getArgument()) {
// The result type of the function must be convertible to the base type.
// TODO: we definitely want this to include ImplicitlyUnwrappedOptional;
// does it need to include everything else in the world?
auto outputTy = CS.createTypeVariable(
CS.getConstraintLocator(expr, ConstraintLocator::FunctionResult),
TVO_CanBindToNoEscape);
CS.addConstraint(ConstraintKind::Conversion, outputTy, baseTy,
CS.getConstraintLocator(expr, ConstraintLocator::RValueAdjustment));
// Create a new type variable for the result of the function.
auto outputLocator = CS.getConstraintLocator(expr,
ConstraintLocator::FunctionResult);
auto outputTy = CS.createTypeVariable(outputLocator,
TVO_CanBindToNoEscape);

// The function/enum case must be callable with the given argument.

Expand All @@ -1481,23 +1509,14 @@ namespace {
CS.getConstraintLocator(expr),
{expr->getArgumentLabels(),
expr->getUnlabeledTrailingClosureIndex()});
return baseTy;
return addUnresolvedMemberChainConstraints(expr, outputTy,
outputLocator);
}

// Otherwise, the member needs to be convertible to the base type.
CS.addConstraint(ConstraintKind::Conversion, memberTy, baseTy,
CS.getConstraintLocator(expr, ConstraintLocator::RValueAdjustment));

// The member type also needs to be convertible to the context type, which
// preserves lvalue-ness.
auto resultTy = CS.createTypeVariable(memberLocator,
TVO_CanBindToLValue |
TVO_CanBindToNoEscape);
CS.addConstraint(ConstraintKind::Conversion, memberTy, resultTy,
memberLocator);
CS.addConstraint(ConstraintKind::Equal, resultTy, baseTy,
memberLocator);
return resultTy;
// Otherwise, add the usual constraints for an element of an unresolved
// member chain.
return addUnresolvedMemberChainConstraints(expr, memberTy, memberLocator,
TVO_CanBindToLValue);
}

Type visitUnresolvedDotExpr(UnresolvedDotExpr *expr) {
Expand Down Expand Up @@ -1561,9 +1580,14 @@ namespace {
return methodTy;
}

return addMemberRefConstraints(expr, expr->getBase(), expr->getName(),
expr->getFunctionRefKind(),
expr->getOuterAlternatives());
auto resultTy = addMemberRefConstraints(expr, expr->getBase(),
expr->getName(),
expr->getFunctionRefKind(),
expr->getOuterAlternatives());

return addUnresolvedMemberChainConstraints(expr, resultTy,
CS.getConstraintLocator(expr, ConstraintLocator::Member),
TVO_CanBindToLValue);
}

Type visitUnresolvedSpecializeExpr(UnresolvedSpecializeExpr *expr) {
Expand Down Expand Up @@ -1722,10 +1746,15 @@ namespace {
if (!isValidBaseOfMemberRef(base, diag::cannot_subscript_nil_literal))
return nullptr;

return addSubscriptConstraints(expr, CS.getType(base),
expr->getIndex(),
decl, expr->getArgumentLabels(),
expr->getUnlabeledTrailingClosureIndex());
auto resultTy = addSubscriptConstraints(expr, CS.getType(base),
expr->getIndex(),
decl, expr->getArgumentLabels(),
expr->getUnlabeledTrailingClosureIndex());
auto resultLocator = CS.getConstraintLocator(expr,
ConstraintLocator::FunctionResult);

return addUnresolvedMemberChainConstraints(expr, resultTy, resultLocator,
TVO_CanBindToLValue);
}

Type visitArrayExpr(ArrayExpr *expr) {
Expand Down Expand Up @@ -2908,6 +2937,15 @@ namespace {
resultType = fixedType;
}

// If the ApplyExpr is a CallExpr, add chain constraints as necessary.
if (isa<CallExpr>(expr)) {
auto resultLocator = CS.getConstraintLocator(expr,
ConstraintLocator::FunctionResult);

return addUnresolvedMemberChainConstraints(expr, resultType,
resultLocator);
}

return resultType;
}

Expand Down Expand Up @@ -3218,7 +3256,8 @@ namespace {
CS.addConstraint(ConstraintKind::OptionalObject,
CS.getType(expr->getSubExpr()), objectTy,
locator);
return objectTy;
return addUnresolvedMemberChainConstraints(expr, objectTy, locator,
TVO_CanBindToLValue);
}

Type visitOptionalEvaluationExpr(OptionalEvaluationExpr *expr) {
Expand Down Expand Up @@ -3253,7 +3292,7 @@ namespace {
CS.addConstraint(ConstraintKind::OptionalObject,
CS.getType(expr->getSubExpr()), objectTy,
locator);
return objectTy;
return addUnresolvedMemberChainConstraints(expr, objectTy, locator);
}

Type visitOpenExistentialExpr(OpenExistentialExpr *expr) {
Expand Down
Loading

0 comments on commit 5b5e30b

Please sign in to comment.