Skip to content

Commit cfa0443

Browse files
committed
Allow literal arguments
1 parent b58e3d8 commit cfa0443

File tree

3 files changed

+89
-17
lines changed

3 files changed

+89
-17
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7530,8 +7530,8 @@ ERROR(conformance_macro,none,
75307530
ERROR(macro_as_default_argument, none,
75317531
"non-built-in macro cannot be used as default argument",
75327532
())
7533-
ERROR(macro_as_default_argument_has_arguments, none,
7534-
"macro used as default argument cannot have arguments",
7533+
ERROR(macro_as_default_argument_arguments_must_be_literal, none,
7534+
"argument to macro used as default argument must be literal",
75357535
())
75367536

75377537
ERROR(macro_resolve_circular_reference, none,

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,33 @@ static void checkInheritedDefaultValueRestrictions(ParamDecl *PD) {
10191019
}
10201020
}
10211021

1022+
static bool checkExpressionMacroDefaultValueRestrictions(ParamDecl *param) {
1023+
assert(param->getDefaultArgumentKind() ==
1024+
DefaultArgumentKind::ExpressionMacro);
1025+
auto &ctx = param->getASTContext();
1026+
auto *initExpr = param->getStructuralDefaultExpr();
1027+
assert(initExpr);
1028+
1029+
// Prohibit default argument that is a non-built-in macro to avoid confusion,
1030+
// unless the experimental feature flag is set.
1031+
if (!ctx.LangOpts.hasFeature(Feature::ExpressionMacroDefaultArguments)) {
1032+
ctx.Diags.diagnose(initExpr->getLoc(), diag::macro_as_default_argument);
1033+
return false;
1034+
}
1035+
1036+
auto macroExpansionExpr = cast<MacroExpansionExpr>(initExpr);
1037+
// only allow arguments that are literals
1038+
auto args = macroExpansionExpr->getArgs();
1039+
for (auto arg : *args)
1040+
if (!dyn_cast<LiteralExpr>(arg.getExpr())) {
1041+
ctx.Diags.diagnose(
1042+
arg.getExpr()->getLoc(),
1043+
diag::macro_as_default_argument_arguments_must_be_literal);
1044+
return false;
1045+
}
1046+
return true;
1047+
}
1048+
10221049
void TypeChecker::notePlaceholderReplacementTypes(Type writtenType,
10231050
Type inferredType) {
10241051
assert(writtenType && inferredType &&
@@ -1132,20 +1159,12 @@ Expr *DefaultArgumentExprRequest::evaluate(Evaluator &evaluator,
11321159
auto *initExpr = param->getStructuralDefaultExpr();
11331160
assert(initExpr);
11341161

1135-
// Prohibit default argument that is a non-built-in macro to avoid confusion,
1136-
auto macroExpansionExpr = dyn_cast<MacroExpansionExpr>(initExpr);
1137-
if (macroExpansionExpr) {
1138-
// unless the experimental feature flag is set.
1139-
if (!ctx.LangOpts.hasFeature(Feature::ExpressionMacroDefaultArguments)) {
1140-
ctx.Diags.diagnose(initExpr->getLoc(), diag::macro_as_default_argument);
1141-
return new (ctx) ErrorExpr(initExpr->getSourceRange(), ErrorType::get(ctx));
1142-
}
1143-
auto args = macroExpansionExpr->getArgs();
1144-
if (!args->empty()) {
1145-
ctx.Diags.diagnose(args->getLoc(), diag::macro_as_default_argument_has_arguments);
1146-
return new (ctx) ErrorExpr(initExpr->getSourceRange(), ErrorType::get(ctx));
1147-
}
1148-
}
1162+
auto isMacroExpansionExpr =
1163+
param->getDefaultArgumentKind() == DefaultArgumentKind::ExpressionMacro;
1164+
1165+
if (isMacroExpansionExpr &&
1166+
!checkExpressionMacroDefaultValueRestrictions(param))
1167+
return new (ctx) ErrorExpr(initExpr->getSourceRange(), ErrorType::get(ctx));
11491168

11501169
// If the param has an error type, there's no point type checking the default
11511170
// expression, unless we are type checking for code completion, in which case
@@ -1163,7 +1182,7 @@ Expr *DefaultArgumentExprRequest::evaluate(Evaluator &evaluator,
11631182
}
11641183

11651184
// Expression macro default arguments are checked at caller side
1166-
if (macroExpansionExpr)
1185+
if (isMacroExpansionExpr)
11671186
return initExpr;
11681187

11691188
// Walk the checked initializer and contextualize any closures
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// REQUIRES: swift_swift_parser
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %host-build-swift -swift-version 5 -emit-library -o %t/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath
5+
6+
// RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) %s -enable-experimental-feature ExpressionMacroDefaultArguments -enable-bare-slash-regex
7+
8+
typealias Stringified<T> = (T, String)
9+
@freestanding(expression)
10+
macro stringify<T>(_ value: T) -> Stringified<T> = #externalMacro(
11+
module: "MacroDefinition", type: "StringifyMacro"
12+
)
13+
14+
struct MyType { }
15+
typealias MyInt = Int
16+
struct MyLiteral: ExpressibleByStringLiteral, _ExpressibleByColorLiteral, _ExpressibleByImageLiteral, _ExpressibleByFileReferenceLiteral {
17+
init(stringLiteral value: StringLiteralType) {}
18+
init(_colorLiteralRed: Float, green: Float, blue: Float, alpha: Float) {}
19+
init(imageLiteralResourceName: String) {}
20+
init(fileReferenceLiteralResourceName: String) {}
21+
}
22+
23+
// MARK: literals
24+
25+
func testNil(nil: Stringified<MyLiteral?> = #stringify(nil)) {}
26+
func testBool(true: Stringified<Bool> = #stringify(true),
27+
false: Stringified<Bool> = #stringify(false)) {}
28+
func testInt(positive: Stringified<UInt64> = #stringify(1_000_001),
29+
zero: Stringified<MyInt> = #stringify(0x0),
30+
negative: Stringified<Int32> = #stringify(-0o21)) {}
31+
func testFloat(double: Stringified<Double> = #stringify(-0xC.3p0),
32+
float: Stringified<Float> = #stringify(00003.14159)) {}
33+
func testString(literal: Stringified<MyLiteral> = #stringify("🐨"),
34+
interpolated: Stringified<String> = #stringify("Hello \(0b10001)")) {}
35+
func testMagic(fileID: Stringified<String> = #stringify(#fileID),
36+
filePath: Stringified<String> = #stringify(#filePath),
37+
file: Stringified<String> = #stringify(#file),
38+
function: Stringified<String> = #stringify(#function),
39+
line: Stringified<Int> = #stringify(#line),
40+
column: Stringified<Int> = #stringify(#column),
41+
dso: Stringified<UnsafeRawPointer> = #stringify(#dsohandle)) {}
42+
@available(SwiftStdlib 5.7, *)
43+
func testRegex(literal: Stringified<Regex<Substring>> = #stringify(/foo/)) {}
44+
func testObject(color: Stringified<MyLiteral> = #stringify(#colorLiteral(red: 0.4, green: 0.8, blue: 1, alpha: 1)),
45+
image: Stringified<MyLiteral> = #stringify(#imageLiteral(resourceName: "swift.png")),
46+
file: Stringified<MyLiteral> = #stringify(#fileLiteral(resourceName: "main.swift"))) {}
47+
48+
// MARK: not literal
49+
50+
let myString = "oops"
51+
52+
// expected-error@+1{{argument to macro used as default argument must be literal}}
53+
func testIdentifier(notOkay: Stringified<String> = #stringify(myString)) {}

0 commit comments

Comments
 (0)