Skip to content

[DNM][requires-evolution] Explicit varargs expansion expression #25997

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

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
62c7f4f
[Parse] Add initial support for parsing a new #variadic directive as …
owenv Jun 30, 2019
ea418d9
[Sema] Tiny CSGen changes needed to support explicit VarargExpansionE…
owenv Jun 30, 2019
0fd419c
Clean up #variadic parsing
owenv Jul 7, 2019
4230aaf
Improve the varargs conversion diag
owenv May 27, 2019
c46b303
Add new fixit offering to wrap arrays with #variadic
owenv Jul 7, 2019
f74b993
better diags for improper #variadic usage
owenv Jul 7, 2019
fde152f
Miscellaneous bug fixes and tests for #variadic
owenv Jul 7, 2019
0b04fdf
Port variadic conversion diagnostics to the new diags framework.
owenv Jul 10, 2019
75debdd
Store the ParameterTypeFlags on ApplyArgToParam constraint locators
owenv Jul 10, 2019
4e4a88b
Remove original CSDiag.cpp implementation of variadic conversion diags
owenv Jul 10, 2019
02a5b12
Post-rebase refactoring of #variadic
owenv Jul 12, 2019
9f5cb7f
Initial implementation of the new `as T...` syntax for array-to-varar…
owenv Jul 13, 2019
0cf9ecd
Remove #variadic syntax in favor of 'as T...'
owenv Jul 13, 2019
eccee1d
Update interpreter test to use the new as T... syntax for varargs exp…
owenv Jul 13, 2019
09d51e5
working on tests and fixes for as T...
owenv Jul 13, 2019
40485ad
fix compiler crash when trying to coerce an array to varargs with a n…
owenv Jul 13, 2019
6bfacf2
Reenable diagnostic requiring as T... only appears in an argument pos…
owenv Jul 13, 2019
0a98fec
Remove old syntax support for the abandoned #variadic syntax
owenv Jul 13, 2019
f64fe96
Fixup most of the as T... diags to reflect the new syntax
owenv Jul 13, 2019
c6b66a8
Conversion diag fixes for as T...
owenv Jul 14, 2019
a5d7a94
Remove the last few references to the #variadic syntax for vararg exp…
owenv Jul 14, 2019
6924cc7
more varargs expansion refactoring
owenv Jul 14, 2019
3736af7
Fix as T... ellipsis parsing bug, add a couple tests to make sure we …
owenv Jul 14, 2019
92a9417
Better parser recovery in the event someone tries to use as! or as? f…
owenv Jul 14, 2019
8667177
Have VarargExpansionExpr forward source locations to its subexpr again
owenv Jul 14, 2019
d83b32f
Add a couple more vararg expansion tests
owenv Jul 14, 2019
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: 3 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,9 @@ ERROR(expected_type_after_is,none,
"expected type after 'is'", ())
ERROR(expected_type_after_as,none,
"expected type after 'as'", ())
ERROR(invalid_ellipsis_after_as_type,none,
"coercion to variadic arguments using '%0' is not allowed; did you mean"
" to use 'as'?", (StringRef))

// Extra tokens in string interpolation like in " >> \( $0 } ) << "
ERROR(string_interpolation_extra,none,
Expand Down
17 changes: 17 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,23 @@ ERROR(cannot_convert_default_arg_value_nil,none,
ERROR(cannot_convert_argument_value,none,
"cannot convert value of type %0 to expected argument type %1",
(Type,Type))

ERROR(cannot_convert_array_to_variadic,none,
"cannot pass an array of type %0 as variadic arguments of type %1",
(Type,Type))
ERROR(vararg_expansion_must_appear_alone,none,
"array elements coerced to variadic arguments cannot be used alongside "
"additional variadic arguments",
())
ERROR(array_to_vararg_coercion_outside_arg_position,none,
"coercion to variadic arguments is only allowed in an argument position",
())

NOTE(suggest_pass_elements_directly,none,
"remove brackets to pass array elements directly", ())
NOTE(suggest_pass_elements_using_vararg_expansion,none,
"use 'as' to pass array elements as variadic arguments", ())

ERROR(cannot_convert_argument_value_generic,none,
"cannot convert value of type %0 (%1) to expected argument type %2 (%3)",
(Type, StringRef, Type, StringRef))
Expand Down
28 changes: 19 additions & 9 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ namespace swift {
class EnumElementDecl;
class CallExpr;
class KeyPathExpr;
class CoerceExpr;

enum class ExprKind : uint8_t {
#define EXPR(Id, Parent) Id,
Expand Down Expand Up @@ -3342,14 +3343,19 @@ class InOutExpr : public Expr {
}
};

/// The not-yet-actually-surfaced '...' varargs expansion operator,
/// which splices an array into a sequence of variadic arguments.
/// The varargs expansion operator, which expands an array into a sequence of
/// variadic arguments. Is written in source as part of a coercion operation,
/// e.g. 'as T...'
class VarargExpansionExpr : public Expr {
Expr *SubExpr;

public:
VarargExpansionExpr(Expr *subExpr, bool implicit, Type type = Type())
: Expr(ExprKind::VarargExpansion, implicit, type), SubExpr(subExpr) {}
: Expr(ExprKind::VarargExpansion, implicit, type), SubExpr(subExpr) {
assert(implicit ||
isa<CoerceExpr>(subExpr) &&
"SubExpr of explicit VarargExpansionExpr must be a CoerceExpr");
}

SWIFT_FORWARD_SOURCE_LOCS_TO(SubExpr)

Expand Down Expand Up @@ -4414,15 +4420,17 @@ class CoerceExpr : public ExplicitCastExpr {
/// we use it to store `start` of the initializer
/// call source range to save some storage.
SourceLoc InitRangeEnd;
bool IncludesVarargExpansion;

public:
CoerceExpr(Expr *sub, SourceLoc asLoc, TypeLoc type)
: ExplicitCastExpr(ExprKind::Coerce, sub, asLoc, type, type.getType())
{ }
CoerceExpr(Expr *sub, SourceLoc asLoc, TypeLoc type,
bool includesVarargExpansion = false)
: ExplicitCastExpr(ExprKind::Coerce, sub, asLoc, type, type.getType()),
IncludesVarargExpansion(includesVarargExpansion) {}

CoerceExpr(SourceLoc asLoc, TypeLoc type)
: CoerceExpr(nullptr, asLoc, type)
{ }
CoerceExpr(SourceLoc asLoc, TypeLoc type,
bool includesVarargExpansion = false)
: CoerceExpr(nullptr, asLoc, type, includesVarargExpansion) {}

private:
CoerceExpr(SourceRange initRange, Expr *literal, TypeLoc type)
Expand All @@ -4441,6 +4449,8 @@ class CoerceExpr : public ExplicitCastExpr {

bool isLiteralInit() const { return InitRangeEnd.isValid(); }

bool includesVarargExpansion() const { return IncludesVarargExpansion; }

SourceRange getSourceRange() const {
return isLiteralInit()
? SourceRange(getAsLoc(), InitRangeEnd)
Expand Down
34 changes: 28 additions & 6 deletions lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ ParserResult<Expr> Parser::parseExprIs() {

/// parseExprAs
/// expr-as:
/// 'as' type
/// 'as' type '...'?
/// 'as?' type
/// 'as!' type
ParserResult<Expr> Parser::parseExprAs() {
Expand All @@ -111,16 +111,38 @@ ParserResult<Expr> Parser::parseExprAs() {
if (type.isNull())
return nullptr;

bool includesVarargExpansion = false;
SourceLoc ellipsisLoc;
auto typeRepr = type.get();
if (Tok.is(tok::oper_postfix) && Tok.isEllipsis()) {
ellipsisLoc = consumeToken();
includesVarargExpansion = true;
typeRepr =
new (Context) ArrayTypeRepr(typeRepr, typeRepr->getSourceRange());
}
ParserStatus status;
Expr *parsed;
if (questionLoc.isValid()) {
parsed = new (Context) ConditionalCheckedCastExpr(asLoc, questionLoc,
type.get());
if (includesVarargExpansion) {
diagnose(asLoc, diag::invalid_ellipsis_after_as_type, "as?")
.fixItRemove(questionLoc);
status.setIsParseError();
}
parsed =
new (Context) ConditionalCheckedCastExpr(asLoc, questionLoc, typeRepr);
} else if (exclaimLoc.isValid()) {
parsed = new (Context) ForcedCheckedCastExpr(asLoc, exclaimLoc, type.get());
if (includesVarargExpansion) {
diagnose(asLoc, diag::invalid_ellipsis_after_as_type, "as!")
.fixItRemove(exclaimLoc);
status.setIsParseError();
}
parsed = new (Context) ForcedCheckedCastExpr(asLoc, exclaimLoc, typeRepr);
} else {
parsed = new (Context) CoerceExpr(asLoc, type.get());
parsed = new (Context) CoerceExpr(asLoc, typeRepr, includesVarargExpansion);
}
return makeParserResult(parsed);
return status.isError() ? makeParserErrorResult(
new (Context) ErrorExpr({asLoc, ellipsisLoc}))
: makeParserResult(parsed);
}

/// parseExprArrow
Expand Down
66 changes: 44 additions & 22 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5318,10 +5318,11 @@ Expr *ExprRewriter::coerceCallArguments(
auto params = funcType->getParams();

// Local function to produce a locator to refer to the given parameter.
auto getArgLocator = [&](unsigned argIdx, unsigned paramIdx)
-> ConstraintLocatorBuilder {
auto getArgLocator =
[&](unsigned argIdx, unsigned paramIdx,
ParameterTypeFlags flags) -> ConstraintLocatorBuilder {
return locator.withPathElement(
LocatorPathElt::getApplyArgToParam(argIdx, paramIdx));
LocatorPathElt::getApplyArgToParam(argIdx, paramIdx, flags));
};

bool matchCanFail =
Expand Down Expand Up @@ -5421,20 +5422,39 @@ Expr *ExprRewriter::coerceCallArguments(
else
newLabelLocs.push_back(SourceLoc());

bool isVarargExpansion = false;
// Convert the arguments.
for (auto argIdx : varargIndices) {
auto arg = getArg(argIdx);
auto argType = cs.getType(arg);

if (isa<VarargExpansionExpr>(arg) && varargIndices.size() > 1) {
tc.diagnose(arg->getLoc(), diag::vararg_expansion_must_appear_alone);
return nullptr;
} else if (isa<VarargExpansionExpr>(arg)) {
isVarargExpansion = true;
}

auto paramType =
isVarargExpansion ? param.getParameterType() : param.getPlainType();

// If the argument type exactly matches, this just works.
if (argType->isEqual(param.getPlainType())) {
if (argType->isEqual(paramType)) {
variadicArgs.push_back(arg);
continue;
}

// Convert the argument.
auto convertedArg = coerceToType(arg, param.getPlainType(),
getArgLocator(argIdx, paramIdx));
Expr *convertedArg;
auto argLocator = getArgLocator(argIdx, paramIdx,
param.getParameterFlags());
if (isVarargExpansion) {
convertedArg = buildCollectionUpcastExpr(arg, paramType, false,
argLocator);
} else {
convertedArg = coerceToType(arg, paramType, argLocator);
}

if (!convertedArg)
return nullptr;

Expand All @@ -5448,21 +5468,22 @@ Expr *ExprRewriter::coerceCallArguments(
end = variadicArgs.back()->getEndLoc();
}

// Collect them into an ArrayExpr.
auto *arrayExpr = ArrayExpr::create(tc.Context,
start,
variadicArgs,
{}, end,
param.getParameterType());
arrayExpr->setImplicit();
cs.cacheType(arrayExpr);
Expr *varargExpansionExpr;
if (variadicArgs.empty() || !isVarargExpansion) {
// Collect them into an ArrayExpr.
auto *arrayExpr = ArrayExpr::create(tc.Context, start, variadicArgs, {},
end, param.getParameterType());
arrayExpr->setImplicit();
cs.cacheType(arrayExpr);

// Wrap the ArrayExpr in a VarargExpansionExpr.
auto *varargExpansionExpr =
new (tc.Context) VarargExpansionExpr(arrayExpr,
/*implicit=*/true,
arrayExpr->getType());
cs.cacheType(varargExpansionExpr);
// Wrap the ArrayExpr in a VarargExpansionExpr.
varargExpansionExpr = new (tc.Context)
VarargExpansionExpr(arrayExpr,
/*implicit=*/true, arrayExpr->getType());
cs.cacheType(varargExpansionExpr);
} else {
varargExpansionExpr = variadicArgs.front();
}

newArgs.push_back(varargExpansionExpr);
newParams.push_back(param);
Expand Down Expand Up @@ -5554,8 +5575,9 @@ Expr *ExprRewriter::coerceCallArguments(
convertedArg = cs.TC.buildAutoClosureExpr(dc, arg, closureType);
cs.cacheExprTypes(convertedArg);
} else {
convertedArg =
coerceToType(arg, paramType, getArgLocator(argIdx, paramIdx));
convertedArg = coerceToType(
arg, paramType,
getArgLocator(argIdx, paramIdx, param.getParameterFlags()));
}

if (!convertedArg)
Expand Down
5 changes: 4 additions & 1 deletion lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2760,7 +2760,10 @@ typeCheckArgumentChildIndependently(Expr *argExpr, Type argType,

resultElts.push_back(elExpr);
auto resFlags =
ParameterTypeFlags().withInOut(elExpr->isSemanticallyInOutExpr());
ParameterTypeFlags()
.withInOut(elExpr->isSemanticallyInOutExpr())
.withVariadic(elExpr->getSemanticsProvidingExpr()->getKind() ==
ExprKind::VarargExpansion);
resultEltTys.push_back({CS.getType(elExpr)->getInOutObjectType(),
TE->getElementName(i), resFlags});
}
Expand Down
62 changes: 58 additions & 4 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -813,10 +813,19 @@ bool MissingForcedDowncastFailure::diagnoseAsError() {
case CheckedCastKind::DictionaryDowncast:
case CheckedCastKind::SetDowncast:
case CheckedCastKind::ValueCast:
emitDiagnostic(coerceExpr->getLoc(), diag::missing_forced_downcast,
fromType, toType)
.highlight(coerceExpr->getSourceRange())
.fixItReplace(coerceExpr->getLoc(), "as!");
auto diag = emitDiagnostic(coerceExpr->getLoc(),
diag::missing_forced_downcast, fromType, toType);
diag.highlight(coerceExpr->getSourceRange());
if (coerceExpr->includesVarargExpansion()) {
diag.fixItInsert(coerceExpr->getStartLoc(), "(");
llvm::SmallString<32> insertAfter;
insertAfter += " as! ";
insertAfter += toType->getWithoutParens()->getString();
insertAfter += ")";
diag.fixItInsertAfter(subExpr->getEndLoc(), insertAfter);
} else {
diag.fixItReplace(coerceExpr->getLoc(), "as!");
}
return true;
}
llvm_unreachable("unhandled cast kind");
Expand Down Expand Up @@ -3493,3 +3502,48 @@ bool MutatingMemberRefOnImmutableBase::diagnoseAsError() {
diagIDmember);
return failure.diagnoseAsError();
}

bool ExpandArrayIntoVarargsFailure::diagnoseAsError() {
if (auto anchor = getAnchor()) {
emitDiagnostic(anchor->getLoc(), diag::cannot_convert_array_to_variadic,
getFromType(), getToType());

// Offer to pass the array elements using 'as T...'
auto *DC = getDC();
auto &TC = getTypeChecker();
auto asPG = getTypeChecker().lookupPrecedenceGroup(
DC, DC->getASTContext().Id_CastingPrecedence, SourceLoc());
// Parens are never needed on the outside because 'as T...' is only allowed
// as an argument
bool needsParensInside =
!asPG || exprNeedsParensInsideFollowingOperator(TC, DC, anchor, asPG);

llvm::SmallString<2> insertBefore;
llvm::SmallString<32> insertAfter;
if (needsParensInside) {
insertBefore += "(";
insertAfter += ")";
}
insertAfter += " as ";
insertAfter += getToType()->getWithoutParens()->getString();
insertAfter += "...";
emitDiagnostic(anchor->getLoc(),
diag::suggest_pass_elements_using_vararg_expansion)
.fixItInsert(anchor->getStartLoc(), insertBefore)
.fixItInsertAfter(anchor->getEndLoc(), insertAfter);

// If this is an array literal, offer to remove the brackets and pass the
// elements directly as variadic arguments.
if (auto *arrayExpr = dyn_cast<ArrayExpr>(anchor)) {
auto diag = emitDiagnostic(arrayExpr->getLoc(),
diag::suggest_pass_elements_directly);
diag.fixItRemove(arrayExpr->getLBracketLoc())
.fixItRemove(arrayExpr->getRBracketLoc());
// Handle the case where the array literal has a trailing comma.
if (arrayExpr->getNumCommas() == arrayExpr->getNumElements())
diag.fixItRemove(arrayExpr->getCommaLocs().back());
}
return true;
}
return false;
}
9 changes: 9 additions & 0 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,15 @@ class SkipUnhandledConstructInFunctionBuilderFailure final
bool diagnoseAsNote() override;
};

class ExpandArrayIntoVarargsFailure final : public ContextualFailure {
public:
ExpandArrayIntoVarargsFailure(Expr *root, ConstraintSystem &cs, Type lhs,
Type rhs, ConstraintLocator *locator)
: ContextualFailure(root, cs, lhs, rhs, locator) {}

bool diagnoseAsError() override;
};

/// Provides information about the application of a function argument to a
/// parameter.
class FunctionArgApplyInfo {
Expand Down
13 changes: 13 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,3 +657,16 @@ AllowMutatingMemberOnRValueBase::create(ConstraintSystem &cs, Type baseType,
return new (cs.getAllocator())
AllowMutatingMemberOnRValueBase(cs, baseType, member, name, locator);
}

ExpandArrayIntoVarargs *
ExpandArrayIntoVarargs::create(ConstraintSystem &cs, Type srcType, Type dstType,
ConstraintLocator *locator) {
return new (cs.getAllocator())
ExpandArrayIntoVarargs(cs, srcType, dstType, locator);
}

bool ExpandArrayIntoVarargs::diagnose(Expr *root, bool asNote) const {
ExpandArrayIntoVarargsFailure failure(
root, getConstraintSystem(), getFromType(), getToType(), getLocator());
return failure.diagnose(asNote);
}
Loading