Skip to content

Commit 16f3306

Browse files
author
Josh Learn
authored
Merge pull request #38053 from guitard0g/fix_closure_constantness_check
[Sema] Fix constantness diagnostics for single expression closures
2 parents ba1c548 + 6c881f5 commit 16f3306

File tree

3 files changed

+141
-6
lines changed

3 files changed

+141
-6
lines changed

lib/Sema/ConstantnessSemaDiagnostics.cpp

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -335,25 +335,56 @@ static void diagnoseConstantArgumentRequirementOfCall(const CallExpr *callExpr,
335335
void swift::diagnoseConstantArgumentRequirement(
336336
const Expr *expr, const DeclContext *declContext) {
337337
class ConstantReqCallWalker : public ASTWalker {
338-
const ASTContext &astContext;
338+
DeclContext *DC;
339339

340340
public:
341-
ConstantReqCallWalker(ASTContext &ctx) : astContext(ctx) {}
341+
ConstantReqCallWalker(DeclContext *DC) : DC(DC) {}
342342

343343
// Descend until we find a call expressions. Note that the input expression
344344
// could be an assign expression or another expression that contains the
345345
// call.
346346
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
347-
if (!expr || isa<ErrorExpr>(expr) || !expr->getType())
347+
// Handle closure expressions separately as we may need to
348+
// manually descend into the body.
349+
if (auto *closureExpr = dyn_cast<ClosureExpr>(expr)) {
350+
return walkToClosureExprPre(closureExpr);
351+
}
352+
// Interpolated expressions' bodies will be type checked
353+
// separately so exit early to avoid duplicate diagnostics.
354+
if (!expr || isa<ErrorExpr>(expr) || !expr->getType() ||
355+
isa<InterpolatedStringLiteralExpr>(expr))
348356
return {false, expr};
349357
if (auto *callExpr = dyn_cast<CallExpr>(expr)) {
350-
diagnoseConstantArgumentRequirementOfCall(callExpr, astContext);
351-
return {false, expr};
358+
diagnoseConstantArgumentRequirementOfCall(callExpr, DC->getASTContext());
352359
}
353360
return {true, expr};
354361
}
362+
363+
std::pair<bool, Expr *> walkToClosureExprPre(ClosureExpr *closure) {
364+
if (closure->hasSingleExpressionBody()) {
365+
// Single expression closure bodies are not visited directly
366+
// by the ASTVisitor, so we must descend into the body manually
367+
// and set the DeclContext to that of the closure.
368+
DC = closure;
369+
return {true, closure};
370+
}
371+
return {false, closure};
372+
}
373+
374+
Expr *walkToExprPost(Expr *expr) override {
375+
if (auto *closureExpr = dyn_cast<ClosureExpr>(expr)) {
376+
// Reset the DeclContext to the outer scope if we descended
377+
// into a closure expr.
378+
DC = closureExpr->getParent();
379+
}
380+
return expr;
381+
}
382+
383+
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
384+
return {true, stmt};
385+
}
355386
};
356387

357-
ConstantReqCallWalker walker(declContext->getASTContext());
388+
ConstantReqCallWalker walker(const_cast<DeclContext *>(declContext));
358389
const_cast<Expr *>(expr)->walk(walker);
359390
}

test/Sema/diag_constantness_check.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,66 @@ func testConstructorAnnotation(x: Int) {
361361
let _ = ConstructorTest(x)
362362
// expected-error@-1 {{argument must be an integer literal}}
363363
}
364+
365+
// Test closure expressions
366+
367+
func funcAcceptingClosure<T>(_ x: () -> T) -> T {
368+
return x()
369+
}
370+
371+
func normalFunction() {}
372+
373+
@_semantics("oslog.requires_constant_arguments")
374+
func constantArgumentFunctionReturningIntCollection(_ constArg: Int) -> Array<Int> {
375+
return [constArg, constArg, constArg]
376+
}
377+
378+
@_semantics("oslog.requires_constant_arguments")
379+
func constantArgumentFunctionReturningInt(_ constArg: Int) -> Int {
380+
return constArg
381+
}
382+
383+
func testCallsWithinClosures(s: String, x: Int) {
384+
funcAcceptingClosure {
385+
constantArgumentFunction(s)
386+
// expected-error@-1 {{argument must be a string literal}}
387+
}
388+
funcAcceptingClosure {
389+
constantArgumentFunction(s)
390+
// expected-error@-1 {{argument must be a string literal}}
391+
constantArgumentFunction(s)
392+
// expected-error@-1 {{argument must be a string literal}}
393+
}
394+
funcAcceptingClosure {
395+
funcAcceptingClosure {
396+
constantArgumentFunction(s)
397+
// expected-error@-1 {{argument must be a string literal}}
398+
}
399+
}
400+
funcAcceptingClosure {
401+
normalFunction()
402+
funcAcceptingClosure {
403+
constantArgumentFunction(s)
404+
// expected-error@-1 {{argument must be a string literal}}
405+
}
406+
}
407+
let _ =
408+
funcAcceptingClosure {
409+
constantArgumentFunctionReturningIntCollection(x)
410+
// expected-error@-1 {{argument must be an integer literal}}
411+
}
412+
.filter { $0 > 0 }
413+
.map { $0 + 1 }
414+
let _ =
415+
funcAcceptingClosure {
416+
constantArgumentFunctionReturningInt(x)
417+
// expected-error@-1 {{argument must be an integer literal}}
418+
} + 10 * x
419+
let _ = { constantArgumentFunctionReturningIntCollection(x) }
420+
// expected-error@-1 {{argument must be an integer literal}}
421+
funcAcceptingClosure {
422+
constantArgumentFunction(1)
423+
constantArgumentFunction("string literal")
424+
constantArgumentFunction("string with a single interpolation \(x)")
425+
}
426+
}

test/Sema/diag_constantness_check_os_log.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,44 @@ func testLogMessageWrappingDiagnostics() {
185185
_osLogTestHelper(nonConstantFunction("key", fallback: "A literal message"))
186186
// expected-error@-1{{argument must be a string interpolation}}
187187
}
188+
189+
// Test closure expressions
190+
191+
func funcAcceptingClosure<T>(_ x: () -> T) -> T {
192+
return x()
193+
}
194+
195+
func normalFunction() {}
196+
197+
func testCallsWithinClosures(formatOpt: OSLogIntegerFormatting) {
198+
funcAcceptingClosure {
199+
_osLogTestHelper("Minimum integer value: \(Int.min, format: formatOpt)")
200+
// expected-error@-1 {{argument must be a static method or property of 'OSLogIntegerFormatting'}}
201+
}
202+
funcAcceptingClosure {
203+
_osLogTestHelper("Minimum integer value: \(Int.min, format: formatOpt)")
204+
// expected-error@-1 {{argument must be a static method or property of 'OSLogIntegerFormatting'}}
205+
_osLogTestHelper("Maximum integer value: \(Int.max, format: formatOpt)")
206+
// expected-error@-1 {{argument must be a static method or property of 'OSLogIntegerFormatting'}}
207+
}
208+
funcAcceptingClosure {
209+
funcAcceptingClosure {
210+
_osLogTestHelper("Minimum integer value: \(Int.min, format: formatOpt)")
211+
// expected-error@-1 {{argument must be a static method or property of 'OSLogIntegerFormatting'}}
212+
}
213+
}
214+
funcAcceptingClosure {
215+
normalFunction()
216+
funcAcceptingClosure {
217+
_osLogTestHelper("Minimum integer value: \(Int.min, format: formatOpt)")
218+
// expected-error@-1 {{argument must be a static method or property of 'OSLogIntegerFormatting'}}
219+
}
220+
}
221+
funcAcceptingClosure {
222+
_osLogTestHelper("Minimum integer value: \(Int.min, format: .hex)")
223+
}
224+
funcAcceptingClosure {
225+
_osLogTestHelper("Minimum integer value: \(Int.min, format: .hex)")
226+
_osLogTestHelper("Maximum integer value: \(Int.max, privacy: .public)")
227+
}
228+
}

0 commit comments

Comments
 (0)