From 7091cd878e7411e002c8b839516112d65f6af70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Rodr=C3=ADguez?= Date: Fri, 3 Oct 2025 13:54:52 -0700 Subject: [PATCH] [Diagnostics] Support @_semantics("swiftui.requires_constant_range") Xcode seems to have some code in `swiftc` that knows how to diagnose incorrect usages of APIs annotated with `@_semantics`. There is around half a dozen different `@_semantics` annotation in `SwiftUI`. The changes here deal with only `swiftui.requires_constant_range`. When the `ForEach` `struct` from `SwiftUI` is used with a `Range` (which conforms to `RandomAccessCollection`) the compiler seems to check for some extra details of the argument which cannot be expressed in the type system, one of them that it should be a compile-time "constant". It seems that part of the code to do so is part of the open source swift.org toolchain, but some other pieces are not. This PR implements to the best of my knowledge what I think the Xcode toolchain might be doing as close as I can. When one builds tooling on top of the open source toolchain one can find cases in which the tooling considers some code valid, while Xcode will consider it invalid, causing confusion. I provide this implementation in the hope that the actual Xcode implementation can be open sourced, because even if SwiftUI itself is not open source, it would be valuable that the `@_semantics` annotations produce the same output in the open source swift.org and the Xcode toolchains. --- include/swift/AST/DiagnosticsSema.def | 7 ++++ include/swift/AST/SemanticAttrs.def | 2 ++ lib/Sema/ConstantnessSemaDiagnostics.cpp | 8 +++++ .../diag_constantness_check_swift_ui.swift | 34 +++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 test/Sema/diag_constantness_check_swift_ui.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 317ec7ff95eb0..888cf00e55f34 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7862,6 +7862,13 @@ ERROR(atomics_ordering_must_be_constant, none, "ordering argument must be a static method or property of %0", (Identifier)) +//------------------------------------------------------------------------------ +// MARK: SwiftUI +//------------------------------------------------------------------------------ + +WARNING(swiftui_arg_must_be_integer_literal, none, + "argument must be an integer literal", ()) + //------------------------------------------------------------------------------ // MARK: access notes //------------------------------------------------------------------------------ diff --git a/include/swift/AST/SemanticAttrs.def b/include/swift/AST/SemanticAttrs.def index 98ada5ecb7a2e..8b7662c08ad2f 100644 --- a/include/swift/AST/SemanticAttrs.def +++ b/include/swift/AST/SemanticAttrs.def @@ -96,6 +96,8 @@ SEMANTICS_ATTR(OSLOG_REQUIRES_CONSTANT_ARGUMENTS, "oslog.requires_constant_argum SEMANTICS_ATTR(OSLOG_LOG_WITH_LEVEL, "oslog.log_with_level") SEMANTICS_ATTR(ATOMICS_REQUIRES_CONSTANT_ORDERINGS, "atomics.requires_constant_orderings") +SEMANTICS_ATTR(SWIFT_UI_REQUIRES_CONSTANT_RANGE, "swiftui.requires_constant_range") + SEMANTICS_ATTR(TYPE_CHECKER_OPEN_EXISTENTIAL, "typechecker._openExistential(_:do:)") SEMANTICS_ATTR(TYPE_CHECKER_TYPE, "typechecker.type(of:)") SEMANTICS_ATTR(TYPE_CHECKER_WITHOUT_ACTUALLY_ESCAPING, "typechecker.withoutActuallyEscaping(_:do:)") diff --git a/lib/Sema/ConstantnessSemaDiagnostics.cpp b/lib/Sema/ConstantnessSemaDiagnostics.cpp index 9357f21070816..df10b5cdfea96 100644 --- a/lib/Sema/ConstantnessSemaDiagnostics.cpp +++ b/lib/Sema/ConstantnessSemaDiagnostics.cpp @@ -76,6 +76,8 @@ static bool isParamRequiredToBeConstant(AbstractFunctionDecl *funcDecl, ParamDec nominal = paramType->getNominalOrBoundGenericNominal(); return !nominal || !isOSLogDynamicObject(nominal); } + if (hasSemanticsAttr(funcDecl, semantics::SWIFT_UI_REQUIRES_CONSTANT_RANGE)) + return param->getParameterName().str() == "data"; if (!hasSemanticsAttr(funcDecl, semantics::ATOMICS_REQUIRES_CONSTANT_ORDERINGS)) return false; @@ -245,6 +247,12 @@ static void diagnoseError(Expr *errorExpr, const ASTContext &astContext, return; } + if (hasSemanticsAttr(funcDecl, + semantics::SWIFT_UI_REQUIRES_CONSTANT_RANGE)) { + diags.diagnose(errorLoc, diag::swiftui_arg_must_be_integer_literal); + return; + } + // Diagnose os_log specific errors here. // Diagnose primitive stdlib types. diff --git a/test/Sema/diag_constantness_check_swift_ui.swift b/test/Sema/diag_constantness_check_swift_ui.swift new file mode 100644 index 0000000000000..ddd35fcdcbadc --- /dev/null +++ b/test/Sema/diag_constantness_check_swift_ui.swift @@ -0,0 +1,34 @@ +// RUN: %target-typecheck-verify-swift + +// Replicating the SwiftUI framework `ForEach` from the SDK +public struct ForEach where Data : Swift.RandomAccessCollection, ID: Swift.Hashable { + public var data: Data + public var content: (Data.Element) -> Content +} + +public protocol View {} +public struct AView: View {} + +extension ForEach where Content : View { + public init(_ data: Data, id: Swift.KeyPath, content: @escaping (Data.Element) -> Content) { + self.data = data + self.content = content + } +} + +extension ForEach where Data == Swift.Range, ID == Swift.Int, Content : View { + @_semantics("swiftui.requires_constant_range") public init(_ data: Swift.Range, content: @escaping (Swift.Int) -> Content) +} + + +func testSwiftUIForEachInvalid() { + let notReallyConstant = 10 + _ = ForEach(0..