Skip to content
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
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,8 @@ ERROR(const_unsupported_type_expr,none,
"type expressions not supported in a constant expression", ())
ERROR(const_unsupported_closure,none,
"closures not supported in a constant expression", ())
ERROR(const_unsupported_closure_with_captures,none,
"closures with captures not supported in a constant expression", ())
ERROR(const_unsupported_keypath,none,
"keypaths not supported in a constant expression", ())
ERROR(const_opaque_decl_ref,none,
Expand Down
72 changes: 68 additions & 4 deletions lib/Sema/LegalConstExprVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum IllegalConstErrorDiagnosis {
TypeExpression,
KeyPath,
Closure,
ClosureWithCaptures,
OpaqueDeclRef,
OpaqueFuncDeclRef,
NonConventionCFunc,
Expand Down Expand Up @@ -82,6 +83,9 @@ static void diagnoseError(const Expr *errorExpr,
case Closure:
diags.diagnose(errorLoc, diag::const_unsupported_closure);
break;
case ClosureWithCaptures:
diags.diagnose(errorLoc, diag::const_unsupported_closure_with_captures);
break;
case OpaqueDeclRef:
diags.diagnose(errorLoc, diag::const_opaque_decl_ref);
break;
Expand Down Expand Up @@ -222,17 +226,23 @@ checkSupportedWithSectionAttribute(const Expr *expr,
if (isa<KeyPathExpr>(expr))
return std::make_pair(expr, KeyPath);

// Closure expressions are not supported in constant expressions
if (isa<AbstractClosureExpr>(expr))
return std::make_pair(expr, Closure);
// Closures are allowed if they have no captures
if (auto closureExpr = dyn_cast<ClosureExpr>(expr)) {
TypeChecker::computeCaptures(const_cast<ClosureExpr *>(closureExpr));
if (!closureExpr->getCaptureInfo().isTrivial()) {
return std::make_pair(expr, ClosureWithCaptures);
}
continue;
}

// Function conversions are allowed if the conversion is to '@convention(c)'
if (auto functionConvExpr = dyn_cast<FunctionConversionExpr>(expr)) {
if (auto targetFnTy =
functionConvExpr->getType()->getAs<AnyFunctionType>()) {
if (targetFnTy->getExtInfo().getRepresentation() ==
FunctionTypeRepresentation::CFunctionPointer) {
expressionsToCheck.push_back(functionConvExpr->getSubExpr());
// Do not check the inner expression -- if it converts to
// CFunctionPointer successfully, then it's constant foldable.
continue;
}
}
Expand All @@ -256,6 +266,60 @@ checkSupportedWithSectionAttribute(const Expr *expr,
return std::make_pair(expr, OpaqueDeclRef);
}

// Allow specific patterns of AutoClosureExpr, which is used in static func
// references. E.g. "MyStruct.staticFunc" is:
// - autoclosure_expr type="() -> ()"
// - call_expr type="()"
// - dot_syntax_call_expr
// - declref_expr decl="MyStruct.staticFunc"
// - dot_self_expr type="MyStruct.Type"
// - type_expr type="MyStruct.Type"
if (auto autoClosureExpr = dyn_cast<AutoClosureExpr>(expr)) {
auto subExpr = autoClosureExpr->getUnwrappedCurryThunkExpr();
if (auto dotSyntaxCall = dyn_cast<DotSyntaxCallExpr>(subExpr)) {
if (auto declRef = dyn_cast<DeclRefExpr>(dotSyntaxCall->getFn())) {
if (auto funcDecl = dyn_cast<FuncDecl>(declRef->getDecl())) {
// Check if it's a function on a concrete non-generic type
if (!funcDecl->hasGenericParamList() &&
!funcDecl->getDeclContext()->isGenericContext() &&
funcDecl->isStatic()) {
if (auto args = dotSyntaxCall->getArgs()) {
if (args->size() == 1) {
// Check that the single arg is a DotSelfExpr with only a
// direct concrete TypeExpr inside
if (auto dotSelfExpr =
dyn_cast<DotSelfExpr>(args->get(0).getExpr())) {
if (const TypeExpr *typeExpr =
dyn_cast<TypeExpr>(dotSelfExpr->getSubExpr())) {
auto baseType = typeExpr->getType();
if (baseType && baseType->is<MetatypeType>()) {
auto instanceType =
baseType->getMetatypeInstanceType();
if (auto nominal =
instanceType
->getNominalOrBoundGenericNominal()) {
if (!nominal->hasGenericParamList() &&
!nominal->getDeclContext()->isGenericContext() &&
!nominal->isResilient()) {
continue;
}
}
}
}
}
}
}
}
}
}
}
return std::make_pair(expr, Default);
}

// Other closure expressions (auto-closures) are not allowed
if (isa<AbstractClosureExpr>(expr))
return std::make_pair(expr, Default);

// DotSelfExpr for metatype references (but only a direct TypeExpr inside)
if (const DotSelfExpr *dotSelfExpr = dyn_cast<DotSelfExpr>(expr)) {
if (const TypeExpr *typeExpr =
Expand Down
20 changes: 20 additions & 0 deletions test/ConstValues/SectionIR.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ func bar(x: Int) -> String { return "test" }
@section("mysection") let funcRef1 = foo // ok
@section("mysection") let funcRef2 = bar // ok

// closures
@section("mysection") let closure1 = { }
@section("mysection") let closure2 = { return 42 }
@section("mysection") let closure3 = { (x: Int) in return x + 1 }
@section("mysection") let closure4: () -> Void = { }
@section("mysection") let closure5: (Int) -> Int = { x in x * 2 }
@section("mysection") let closure6: @convention(c) (Int) -> Int = { x in x * 2 }
struct W {
@section("mysection") static let closure7: @convention(c) (Int) -> Int = { x in x * 2 }
}

// metatypes - TODO
//@section("mysection") let metatype1 = Int.self

Expand Down Expand Up @@ -56,6 +67,15 @@ func bar(x: Int) -> String { return "test" }
// CHECK: @"$s9SectionIR12boolLiteral2Sbvp" = {{.*}}constant %TSb zeroinitializer, section "mysection"
// CHECK: @"$s9SectionIR8funcRef1Siycvp" = {{.*}}constant %swift.function { ptr @"$s9SectionIR3fooSiyF{{.*}}", ptr null }, section "mysection"
// CHECK: @"$s9SectionIR8funcRef2ySSSicvp" = {{.*}}constant %swift.function { ptr @"$s9SectionIR3bar1xSSSi_tF{{.*}}", ptr null }, section "mysection"

// CHECK: @"$s9SectionIR8closure1yycvp" = {{.*}}constant %swift.function { {{.*}} }, section "mysection"
// CHECK: @"$s9SectionIR8closure2Siycvp" = {{.*}}constant %swift.function { {{.*}} }, section "mysection"
// CHECK: @"$s9SectionIR8closure3yS2icvp" = {{.*}}constant %swift.function { {{.*}} }, section "mysection"
// CHECK: @"$s9SectionIR8closure4yycvp" = {{.*}}constant %swift.function { {{.*}} }, section "mysection"
// CHECK: @"$s9SectionIR8closure5yS2icvp" = {{.*}}constant %swift.function { {{.*}} }, section "mysection"
// CHECK: @"$s9SectionIR8closure6yS2iXCvp" = {{.*}}constant ptr @"$s9SectionIR8closure6yS2iXCvpfiS2icfU_To", section "mysection"
// CHECK: @"$s9SectionIR1WV8closure7yS2iXCvpZ" = {{.*}}constant ptr @"$s9SectionIR1WV8closure7yS2iXCvpZfiS2icfU_To", section "mysection"

// CHECK: @"$s9SectionIR6tuple1Si_S2iSdSbtvp" = {{.*}}constant <{ %TSi, %TSi, %TSi, {{.*}} }> <{ %TSi <{ {{i64|i32}} 1 }>, %TSi <{ {{i64|i32}} 2 }>, %TSi <{ {{i64|i32}} 3 }>, {{.*}} }>, section "mysection"
// CHECK: @"$s9SectionIR6tuple2Si_SfSbtvp" = {{.*}}constant <{ %TSi, %TSf, %TSb }> <{ %TSi <{ {{i64|i32}} 42 }>, %TSf <{ float 0x40091EB860000000 }>, %TSb zeroinitializer }>, section "mysection"
// CHECK: @"$s9SectionIR6tuple3Siyc_SSSictvp" = {{.*}}constant <{ %swift.function, %swift.function }> <{ %swift.function { ptr @"$s9SectionIR3fooSiyF{{.*}}", ptr null }, %swift.function { ptr @"$s9SectionIR3bar1xSSSi_tF{{.*}}", ptr null } }>, section "mysection"
Expand Down
71 changes: 64 additions & 7 deletions test/ConstValues/SectionSyntactic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,86 @@
func foo() -> Int { return 42 }
func bar(x: Int) -> String { return "test" }

// function references
// global function references
@section("mysection") let funcRef1 = foo // ok
@section("mysection") let funcRef2 = bar // ok
@section("mysection") let funcRef3: ()->Int = foo // ok
@section("mysection") let funcRef4: @convention(c) ()->Int = foo // ok

struct Q {
func memberFunc() {}
static func staticFunc() {}
func genericFunc<T>(t: T) {}
static func staticGenericFunc<T>(t: T) {}
}

// non-global function references
@section("mysection") let staticFuncRef1 = Q.staticFunc // ok
extension Q { @section("mysection") static let staticFuncRef2 = staticFunc } // ok
@section("mysection") let staticFuncRef3 = Bool.random // ok

// invalid function references (should be rejected)
@section("mysection") let invalidFuncRef1 = foo()
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidFuncRef2 = Bool.self.random
// expected-error@-1{{closures not supported in a constant expression}}
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidFuncRef3 = (Bool.self as Bool.Type).random
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidFuncRef4 = Q.memberFunc
// expected-error@-1{{not supported in a constant expression}}
extension Q { @section("mysection") static let invalidFuncRef5 = memberFunc }
// expected-error@-1{{not supported in a constant expression}}
extension Q { @section("mysection") static let invalidFuncRef6: (Int)->() = genericFunc(Q()) }
// expected-error@-1{{not supported in a constant expression}}
extension Q { @section("mysection") static let invalidFuncRef7: (Q) -> (Int) -> () = genericFunc }
// expected-error@-1{{not supported in a constant expression}}
extension Q { @section("mysection") static let invalidFuncRef8: (Int)->() = staticGenericFunc }
// expected-error@-1{{not supported in a constant expression}}

// generic function references (should be rejected)
@section("mysection") let invalidGenericFunc = [Int].randomElement
// expected-error@-1{{not supported in a constant expression}}

// closures (should be rejected)
@section("mysection") let invalidClosure1 = { }
// expected-error@-1{{closures not supported in a constant expression}}
@section("mysection") let invalidClosure2 = { return 42 }
// expected-error@-1{{closures not supported in a constant expression}}
// closures
@section("mysection") let closure1 = { } // ok
@section("mysection") let closure2 = { return 42 } // ok
@section("mysection") let closure3 = { (x: Int) in return x + 1 } // ok
@section("mysection") let closure4: () -> Void = { } // ok
@section("mysection") let closure5: (Int) -> Int = { x in x * 2 } // ok
@section("mysection") let closure6: @convention(c) (Int) -> Int = { x in x * 2 } // ok
struct W {
@section("mysection") static let closure7: @convention(c) (Int) -> Int = { x in x * 2 } // ok
}
func g() {
@Sendable func f() { }
@Sendable func h() async { }
enum E {
@section("mysection") static let record: @convention(c) () -> () = { f() } // ok
@section("mysection") static let record2: @convention(c) () -> () = { _ = h } // ok
}
}

let capturedVar = 10
class TestClass {}
var capturedMutableVar = TestClass()

// closures with captures (should be rejected)
@section("mysection") let invalidClosure1 = { capturedVar }
// expected-error@-1{{closures with captures not supported in a constant expression}}
@section("mysection") let invalidClosure2 = { return capturedVar + 1 }
// expected-error@-1{{closures with captures not supported in a constant expression}}
@section("mysection") let invalidClosure3 = { [capturedVar] in return capturedVar }
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidClosure4 = { [weak capturedMutableVar] in return capturedMutableVar }
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidClosure5 = { [unowned capturedMutableVar] in return capturedMutableVar }
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidClosure6 = { [capturedVar, capturedMutableVar] in return 42 }
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidClosure7 = { [renamed = capturedVar] in return renamed * 2 }
// expected-error@-1{{not supported in a constant expression}}
@section("mysection") let invalidClosure8 = { [computed = capturedVar + 5] in return computed }
// expected-error@-1{{not supported in a constant expression}}

struct S { }
enum E { case a }
Expand Down