Skip to content

[ConstraintSystem] Rework handling of object literal expressions #32481

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

Merged
merged 4 commits into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -2885,6 +2885,10 @@ class AnyFunctionType : public TypeBase {

Param getWithoutLabel() const { return Param(Ty, Identifier(), Flags); }

Param withLabel(Identifier newLabel) const {
return Param(Ty, newLabel, Flags);
}

Param withType(Type newType) const { return Param(newType, Label, Flags); }

Param withFlags(ParameterTypeFlags flags) const {
Expand Down
27 changes: 21 additions & 6 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2533,14 +2533,15 @@ namespace {
if (cs.getType(expr) && !cs.getType(expr)->hasTypeVariable())
return expr;

auto &ctx = cs.getASTContext();

// Figure out the type we're converting to.
auto openedType = cs.getType(expr);
auto type = simplifyType(openedType);
cs.setType(expr, type);

if (type->is<UnresolvedType>()) return expr;
if (type->is<UnresolvedType>())
return expr;

auto &ctx = cs.getASTContext();

Type conformingType = type;
if (auto baseType = conformingType->getOptionalObjectType()) {
Expand All @@ -2550,7 +2551,7 @@ namespace {
}

// Find the appropriate object literal protocol.
auto proto = TypeChecker::getLiteralProtocol(cs.getASTContext(), expr);
auto proto = TypeChecker::getLiteralProtocol(ctx, expr);
assert(proto && "Missing object literal protocol?");
auto conformance =
TypeChecker::conformsToProtocol(conformingType, proto, cs.DC);
Expand All @@ -2560,10 +2561,24 @@ namespace {

ConcreteDeclRef witness = conformance.getWitnessByName(
conformingType->getRValueType(), constrName);
if (!witness || !isa<AbstractFunctionDecl>(witness.getDecl()))

auto selectedOverload = solution.getOverloadChoiceIfAvailable(
cs.getConstraintLocator(expr, ConstraintLocator::ConstructorMember));

if (!selectedOverload)
return nullptr;

auto fnType =
simplifyType(selectedOverload->openedType)->castTo<FunctionType>();

auto newArg = coerceCallArguments(
expr->getArg(), fnType, witness,
/*applyExpr=*/nullptr, expr->getArgumentLabels(),
expr->hasTrailingClosure(),
cs.getConstraintLocator(expr, ConstraintLocator::ApplyArgument));

expr->setInitializer(witness);
expr->setArg(cs.coerceToRValue(expr->getArg()));
expr->setArg(newArg);
return expr;
}

Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1110,8 +1110,7 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const {
fix = SpecifyClosureParameterType::create(cs, dstLocator);
} else if (TypeVar->getImpl().isClosureResultType()) {
fix = SpecifyClosureReturnType::create(cs, dstLocator);
} else if (srcLocator->getAnchor() &&
isExpr<ObjectLiteralExpr>(srcLocator->getAnchor())) {
} else if (srcLocator->directlyAt<ObjectLiteralExpr>()) {
fix = SpecifyObjectLiteralTypeImport::create(cs, dstLocator);
} else if (srcLocator->isKeyPathRoot()) {
fix = SpecifyKeyPathRootType::create(cs, dstLocator);
Expand Down
55 changes: 29 additions & 26 deletions lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1357,60 +1357,63 @@ namespace {
if (expr->getType())
return expr->getType();

auto &de = CS.getASTContext().Diags;
auto protocol = TypeChecker::getLiteralProtocol(CS.getASTContext(), expr);
auto &ctx = CS.getASTContext();
auto &de = ctx.Diags;
auto protocol = TypeChecker::getLiteralProtocol(ctx, expr);
if (!protocol) {
de.diagnose(expr->getLoc(), diag::use_unknown_object_literal_protocol,
expr->getLiteralKindPlainName());
return nullptr;
}

auto tv = CS.createTypeVariable(exprLoc,
TVO_PrefersSubtypeBinding |
TVO_CanBindToNoEscape |
TVO_CanBindToHole);

CS.addConstraint(ConstraintKind::LiteralConformsTo, tv,
protocol->getDeclaredType(),
exprLoc);
auto witnessType = CS.createTypeVariable(
exprLoc, TVO_PrefersSubtypeBinding | TVO_CanBindToNoEscape |
TVO_CanBindToHole);

CS.addConstraint(ConstraintKind::LiteralConformsTo, witnessType,
protocol->getDeclaredType(), exprLoc);

// The arguments are required to be argument-convertible to the
// idealized parameter type of the initializer, which generally
// simplifies the first label (e.g. "colorLiteralRed:") by stripping
// all the redundant stuff about literals (leaving e.g. "red:").
// Constraint application will quietly rewrite the type of 'args' to
// use the right labels before forming the call to the initializer.
auto constrName =
TypeChecker::getObjectLiteralConstructorName(CS.getASTContext(),
expr);
auto constrName = TypeChecker::getObjectLiteralConstructorName(ctx, expr);
assert(constrName);
auto *constr = dyn_cast_or_null<ConstructorDecl>(
protocol->getSingleRequirement(constrName));
if (!constr) {
de.diagnose(protocol, diag::object_literal_broken_proto);
return nullptr;
}
auto constrParamType =
TypeChecker::getObjectLiteralParameterType(expr, constr);

// Extract the arguments.
auto *memberLoc =
CS.getConstraintLocator(expr, ConstraintLocator::ConstructorMember);

auto *memberType =
CS.createTypeVariable(memberLoc, TVO_CanBindToNoEscape);

CS.addValueMemberConstraint(MetatypeType::get(witnessType, ctx),
DeclNameRef(constrName), memberType, CurDC,
FunctionRefKind::DoubleApply, {}, memberLoc);

SmallVector<AnyFunctionType::Param, 8> args;
AnyFunctionType::decomposeInput(CS.getType(expr->getArg()), args);

// Extract the parameters.
SmallVector<AnyFunctionType::Param, 8> params;
AnyFunctionType::decomposeInput(constrParamType, params);
auto resultType = CS.createTypeVariable(
CS.getConstraintLocator(expr, ConstraintLocator::FunctionResult),
TVO_CanBindToNoEscape);

auto funcType = constr->getMethodInterfaceType()->castTo<FunctionType>();
::matchCallArguments(
CS, funcType, args, params, ConstraintKind::ArgumentConversion,
CS.getConstraintLocator(expr, ConstraintLocator::ApplyArgument));
CS.addConstraint(
ConstraintKind::ApplicableFunction,
FunctionType::get(args, resultType), memberType,
CS.getConstraintLocator(expr, ConstraintLocator::ApplyFunction));

Type result = tv;
if (constr->isFailable())
result = OptionalType::get(result);
return OptionalType::get(witnessType);

return result;
return witnessType;
}

Type visitDeclRefExpr(DeclRefExpr *E) {
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6754,7 +6754,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
return SolutionKind::Solved;
} else if ((kind == ConstraintKind::ValueMember ||
kind == ConstraintKind::ValueWitness) &&
baseObjTy->isHole()) {
baseObjTy->getMetatypeInstanceType()->isHole()) {
// If base type is a "hole" there is no reason to record any
// more "member not found" fixes for chained member references.
markMemberTypeAsPotentialHole(memberTy);
Expand Down
31 changes: 31 additions & 0 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,9 @@ ConstraintLocator *ConstraintSystem::getCalleeLocator(
if (isExpr<MemberRefExpr>(anchor))
return getConstraintLocator(anchor, ConstraintLocator::Member);

if (isExpr<ObjectLiteralExpr>(anchor))
return getConstraintLocator(anchor, ConstraintLocator::ConstructorMember);

return getConstraintLocator(anchor);
}

Expand Down Expand Up @@ -1666,6 +1669,34 @@ ConstraintSystem::getTypeOfMemberReference(
});
}

// Construct an idealized parameter type of the initializer associated
// with object literal, which generally simplifies the first label
// (e.g. "colorLiteralRed:") by stripping all the redundant stuff about
// literals (leaving e.g. "red:").
{
auto anchor = locator.getAnchor();
if (auto *OLE = getAsExpr<ObjectLiteralExpr>(anchor)) {
auto fnType = type->castTo<FunctionType>();

SmallVector<AnyFunctionType::Param, 4> params(fnType->getParams().begin(),
fnType->getParams().end());

switch (OLE->getLiteralKind()) {
case ObjectLiteralExpr::colorLiteral:
params[0] = params[0].withLabel(Context.getIdentifier("red"));
break;

case ObjectLiteralExpr::fileLiteral:
case ObjectLiteralExpr::imageLiteral:
params[0] = params[0].withLabel(Context.getIdentifier("resourceName"));
break;
}

type =
FunctionType::get(params, fnType->getResult(), fnType->getExtInfo());
}
}

// If we opened up any type variables, record the replacements.
recordOpenedTypes(locator, replacements);

Expand Down
36 changes: 0 additions & 36 deletions lib/Sema/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,42 +174,6 @@ DeclName TypeChecker::getObjectLiteralConstructorName(ASTContext &Context,
llvm_unreachable("unknown literal constructor");
}

/// Return an idealized form of the parameter type of the given
/// object-literal initializer. This removes references to the protocol
/// name from the first argument label, which would be otherwise be
/// redundant when writing out the object-literal syntax:
///
/// #fileLiteral(fileReferenceLiteralResourceName: "hello.jpg")
///
/// Doing this allows us to preserve a nicer (and source-compatible)
/// literal syntax while still giving the initializer a semantically
/// unambiguous name.
Type TypeChecker::getObjectLiteralParameterType(ObjectLiteralExpr *expr,
ConstructorDecl *ctor) {
auto params = ctor->getMethodInterfaceType()
->castTo<FunctionType>()->getParams();
SmallVector<AnyFunctionType::Param, 8> newParams;
newParams.append(params.begin(), params.end());

auto replace = [&](StringRef replacement) -> Type {
auto &Context = ctor->getASTContext();
newParams[0] = AnyFunctionType::Param(newParams[0].getPlainType(),
Context.getIdentifier(replacement),
newParams[0].getParameterFlags());
return AnyFunctionType::composeInput(Context, newParams,
/*canonicalVararg=*/false);
};

switch (expr->getLiteralKind()) {
case ObjectLiteralExpr::colorLiteral:
return replace("red");
case ObjectLiteralExpr::fileLiteral:
case ObjectLiteralExpr::imageLiteral:
return replace("resourceName");
}
llvm_unreachable("unknown literal constructor");
}

ModuleDecl *TypeChecker::getStdlibModule(const DeclContext *dc) {
if (auto *stdlib = dc->getASTContext().getStdlibModule()) {
return stdlib;
Expand Down
3 changes: 0 additions & 3 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -1057,9 +1057,6 @@ ProtocolDecl *getLiteralProtocol(ASTContext &ctx, Expr *expr);
DeclName getObjectLiteralConstructorName(ASTContext &ctx,
ObjectLiteralExpr *expr);

Type getObjectLiteralParameterType(ObjectLiteralExpr *expr,
ConstructorDecl *ctor);

/// Get the module appropriate for looking up standard library types.
///
/// This is "Swift", if that module is imported, or the current module if
Expand Down
5 changes: 4 additions & 1 deletion test/Sema/object_literals_ios.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ struct S: _ExpressibleByColorLiteral {
let y: S = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)
let y2 = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1) // expected-error{{could not infer type of color literal}}
// expected-note@-1{{import UIKit to use 'UIColor' as the default color literal type}}
let y3 = #colorLiteral(red: 1, bleen: 0, grue: 0, alpha: 1) // expected-error{{incorrect argument labels in call (have 'red:bleen:grue:alpha:', expected 'red:green:blue:alpha:')}}
let y3 = #colorLiteral(red: 1, bleen: 0, grue: 0, alpha: 1)
// expected-error@-1 {{could not infer type of color literal}}
// expected-note@-2 {{import UIKit to use 'UIColor' as the default color literal type}}

let _: S = #colorLiteral(red: 1, bleen: 0, grue: 0, alpha: 1)
// expected-error@-1{{incorrect argument labels in call (have 'red:bleen:grue:alpha:', expected 'red:green:blue:alpha:')}} {{34-39=green}} {{44-48=blue}}

struct I: _ExpressibleByImageLiteral {
init(imageLiteralResourceName: String) {}
}
Expand Down
11 changes: 8 additions & 3 deletions test/Sema/object_literals_osx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ struct S: _ExpressibleByColorLiteral {
let y: S = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)
let y2 = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1) // expected-error{{could not infer type of color literal}}
// expected-note@-1{{import AppKit to use 'NSColor' as the default color literal type}}
let y3 = #colorLiteral(red: 1, bleen: 0, grue: 0, alpha: 1) // expected-error{{incorrect argument labels in call (have 'red:bleen:grue:alpha:', expected 'red:green:blue:alpha:')}}
let y3 = #colorLiteral(red: 1, bleen: 0, grue: 0, alpha: 1)
// expected-error@-1{{could not infer type of color literal}}
// expected-note@-2{{import AppKit to use 'NSColor' as the default color literal type}}

let _: S = #colorLiteral(red: 1, bleen: 0, grue: 0, alpha: 1)
// expected-error@-1 {{incorrect argument labels in call (have 'red:bleen:grue:alpha:', expected 'red:green:blue:alpha:')}} {{34-39=green}} {{44-48=blue}}

struct I: _ExpressibleByImageLiteral {
init(imageLiteralResourceName: String) {}
}
Expand All @@ -21,7 +24,7 @@ let z2 = #imageLiteral(resourceName: "hello.png") // expected-error{{could not i
// expected-note@-1{{import AppKit to use 'NSImage' as the default image literal type}}

struct Path: _ExpressibleByFileReferenceLiteral {
init(fileReferenceLiteralResourceName: String) {}
init(fileReferenceLiteralResourceName: String) {} // expected-note {{'init(fileReferenceLiteralResourceName:)' declared here}}
}

let p1: Path = #fileLiteral(resourceName: "what.txt")
Expand All @@ -32,10 +35,12 @@ let text = #fileLiteral(resourceName: "TextFile.txt").relativeString! // expecte
// expected-note@-1{{import Foundation to use 'URL' as the default file reference literal type}}

// rdar://problem/49861813
#fileLiteral() // expected-error{{missing argument for parameter 'resourceName' in call}}
#fileLiteral()
// expected-error@-1{{could not infer type of file reference literal}}
// expected-note@-2{{import Foundation to use 'URL' as the default file reference literal type}}

let _: Path = #fileLiteral() // expected-error {{missing argument for parameter 'resourceName' in call}}

// rdar://problem/62927467
func test_literal_arguments_are_loaded() {
var resource = "foo.txt" // expected-warning {{variable 'resource' was never mutated; consider changing to 'let' constant}}
Expand Down