How to test if swiftc supports an upcoming/experimental feature?

Is it possible to test on the command line whether swiftc supports a specific upcoming or experimental feature, e.g. "Embedded"? Ideally, something like this:

$ swiftc -supports-experimental-feature Embedded
# Exit code signals yes or no

I was hoping I could use the -emit-supported-features flag for this, but that doesn't seem to work as I expect (or at all?):

$ swiftc --help
  …
  -emit-supported-features   Emit a JSON file including all supported compiler features
  …

$ swiftc -emit-supported-features
SwiftDriver/Driver.swift:2043: Fatal error: unhandled output mode option -emit-supported-features
fish: Job 1, 'swiftc -emit-supported-features' terminated by signal SIGTRAP (Trace or breakpoint trap)

swiftc --version
swift-driver version: 1.87.3 Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-macosx13.0

Possibly related discussion on GitHub (people seem to be unclear how -emit-supported-features is supposed to work): SR-14256: fixes swift-frontend crash by tschuett · Pull Request #36086 · apple/swift · GitHub

My workaround for testing for Embedded support

My current workaround is to test if the compiler supports a Swift Embedded target:

swiftc -print-target-info -target armv6m-none-none-eabi

If this exits with an error code, I'm treating it as equivalent to "doesn't support Embedded", which seems to work, but isn't a general solution.

You could accomplish this using this dirty hack that involves writing a shell script for checking a feature:

#!/bin/sh

swift <<END_OF_SWIFT_SCRIPT
import Foundation
#if hasFeature($1)
    exit(EXIT_SUCCESS)
#else
    exit(EXIT_FAILURE)
#endif
END_OF_SWIFT_SCRIPT
2 Likes

IIUC swiftc -emit-supported-features flag is for frontend features, not the actual language features, and in the end that's supposed to read information from /usr/share/swift/features.json file, which is at that exact path on swift Docker images, or in the corresponding toolchain directory with Xcode installations. Maybe the help description of that option could be updated to make it less confusing?

1 Like

Good idea, I didn’t think of this. Thanks!

1 Like

Thanks, this clears things up.

Yeah, maybe. Admittedly, the help description does make sense as it mentions compiler features as opposed to language features. I guess I was expecting the feature I wanted to exist, so I didn’t consider other types of features.

@technogen I think your script is missing a hyphen here:

- swift <<END_OF_SWIFT_SCRIPT
+ swift - <<END_OF_SWIFT_SCRIPT

On second thought, your script isn't what I want. The compile-time check hasFeature only succeeds for features that are active when the file is being compiled. So this will return a failure for all upcoming Swift 6 features unless you explicitly activate the feature, e.g. with -enable-upcoming-feature BareSlashRegexLiterals.

I wanted to know if the compiler knows about a particular feature at all, i.e. if it is a valid input for -enable-upcoming-feature or -enable-experimental-feature.

I'd love it if the compiler could just return a list of all upcoming and experimental features it knows about.

1 Like

Oops! You're right.

Sounds like, the list of compiler features is what you want. Just make sure to check out the commit relevant to the version of the compiler you're using.

1 Like

That would be nice.

Not what you want, but for reference:

https://github.com/apple/swift/blob/main/include/swift/Basic/Features.def

1 Like

Thanks. I know about Features.def, but as you say, that doesn't help if you want to test dynamically if a given instance of swiftc supports a certain feature, e.g. in a script.

At some point, a dirty hack suddenly becomes an impressive trick! :grin:

swift-feature-list.sh
#!/bin/sh

CURRENT_SWIFT_VERSION="$(swift --version 2>/dev/null | sed -En -e 's/.*Swift version ([^[:space:]]+).*/\1/p')"
SWIFT_COMPILER_REPOSITORY_TAG_FOR_CURRENT_SWIFT_VERSION="swift-${CURRENT_SWIFT_VERSION}-RELEASE"
GITHUB_URL_FOR_FEATURE_DEFINITION_FILE_FOR_CURRENT_SWIFT_VERSION="https://raw.githubusercontent.com/apple/swift/${SWIFT_COMPILER_REPOSITORY_TAG_FOR_CURRENT_SWIFT_VERSION}/include/swift/Basic/Features.def"
FEATURE_DEFINITION_FILE_FOR_CURRENT_SWIFT_VERSION="$(curl --silent "${GITHUB_URL_FOR_FEATURE_DEFINITION_FILE_FOR_CURRENT_SWIFT_VERSION}")"

clang --preprocess --no-line-commands -nostdinc -x c - <<END_OF_FILE
    #define LANGUAGE_FEATURE(FeatureName, SENumber, Description, Option) FeatureName
    ${FEATURE_DEFINITION_FILE_FOR_CURRENT_SWIFT_VERSION}
END_OF_FILE
2 Likes

Very creative, thanks!

After thinking about this some more, it occurred to me that I could actually use your first solution with a small change:

  #!/bin/sh
  
- swift <<END_OF_SWIFT_SCRIPT
+ swift -enable-upcoming-feature $1 -enable-experimental-feature $1 - <<END_OF_SWIFT_SCRIPT
  import Foundation
  #if hasFeature($1)
      exit(EXIT_SUCCESS)
  #else
      exit(EXIT_FAILURE)
  #endif
  END_OF_SWIFT_SCRIPT

By explicitly enabling the feature, the compile-time check with hasFeature does what I want.

My final script adds the option to pass in the path to the Swift compiler and also prints the compiler version:

#!/bin/zsh

# Test if the Swift compiler knows about a particular language feature.
#
# Usage:
#
#     swift-has-feature [--swiftc SWIFTC_PATH] FEATURE
#
# The exit code signals success (= the compiler knows this feature) or failure.
#
# Example:
#
#     swift-has-feature FullTypedThrows
#     swift-has-feature --swiftc /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-01-04-a.xctoolchain/usr/bin/swift FullTypedThrows

usage="swift-has-feature [--swift <path_to_swift>] [--silent] <feature>"

zmodload zsh/zutil
zparseopts -D -F -- -swift:=arg_swift_path -silent=flag_silent || exit 1

if test -z "$1"; then
    echo "Usage: $usage"
    exit
fi

swift_path=${arg_swift_path[-1]}
if test -z "$swift_path"; then
    swift_path="swift"
fi

# Print compiler version
if test -z "$flag_silent"; then
    "$swift_path" --version
fi

"$swift_path" \
    -enable-upcoming-feature "$1" \
    -enable-experimental-feature "$1" \
    - << END_OF_SWIFT_SCRIPT
import Foundation

#if hasFeature($1)
    exit(EXIT_SUCCESS)
#else
    exit(EXIT_FAILURE)
#endif
END_OF_SWIFT_SCRIPT

if test $? -eq 0; then
    if test -z "$flag_silent"; then
        echo "Supported: Swift compiler knows feature '$1'"
    fi
else
    if test -z "$flag_silent"; then
        echo "Not supported: Swift compiler doesn’t know feature '$1'"
    fi
    exit 1
fi

Sample output with two different compiler versions:

$ swift-has-feature FullTypedThrows
swift-driver version: 1.87.3 Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-macosx13.0
Not supported: Swift compiler doesn’t know feature 'FullTypedThrows'

$ swift-has-feature --swift /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-01-04-a.xctoolchain/usr/bin/swift FullTypedThrows
Apple Swift version 5.11-dev (LLVM 1eeb3faad9a9a2a, Swift e2f888b311665f1)
Target: arm64-apple-macosx13.0
Supported: Swift compiler knows feature 'FullTypedThrows'

Thank you all again for your help.

3 Likes