The capability itself is quite welcome, being able for a file to opt into some feature or mode is useful, however I'm pretty wary of this spelling of it.
I'll start with what feels not quite right with spelling these as "compiler settings":
- it looks like top level code. Like some function call, but it magically isn't;
- Do we really need some arbitrary one off syntax for this?
- I'm very uncomfortable with special syntax for something that totally looks like "normal top level statements" to actually be compile time configuration, and it doesn't really seem like this specific spelling really gets us some specific benefit (?) like IDE tooling etc, they'd still need to be thought that this is "special"
- @Sajjon makes good points here that I mostly agree with and will expand on below; and @Slava_Pestov also brings up the potential for source break (even if technically unlikely)
- I don't think we should be focusing on the "compiler" angle of these settings (in the naming)
- The fact that these are eventually turned into some set of flags/options passed to the compiler frontend feels somewhat insignificant, we're talking about aspects of the language and how to influence it IMHO
"Importing" modes and features?
There exists some similar prior art in other languages that may be worth leaning into a bit:
Scala has a feature called language imports, where one would "import language.{something}
" language features. It's been part of Scala for a long time, since 2.12, proposal here: SIP: Modularizing Language Features - Google Docs and features it enables are e.g. (just to give an idea):
- existentials - enables writing existential types
- higherKinds - enables writing higher-kinded types
- ...
experimental
- contains newer features that have not yet been tested in production
- etc.
So since import can use .
to dig into objects, a typical enabling of some feature might look like:
// scala reference example
import language.experimental.something
One problem that might (or might not) appear with this is that it would be weird to pass arguments to these options. While for something like language.warningsAsErrors
and language.warningsAsErrors.concurrency
this might be ok since we can do namespaces (if we extended import to survive multiple .
, I don't think it can today AFAIR). But the more I think about it the less I am concerned, these options should not become overly complex and just treating them as on/off flags may be better anyway -- and this way we stop ourselves from inventing very complex modes...
Another alternative to think about, if we want to make sure this is distinct from "just normal imports", we already have such way to classify imports: import struct
is a thing, so we could do
import feature defaultIsolation.mainActor
or similar, to signify what this is doing. This would be my preferred way precisely because it is limited to be simple and we're extending an existing notion in the language.
The reason to consider import
for this is because:
import
is well understood as affecting how a file is interpreted, and to me (and prior art mentioned above) it really feels natural to build on this
- I think it is useful to keep the power of this feature limited to be honest... The proposal states
.defaultIsolation(MainActor.self),
and I'm wondering if that would actually really work... we'd have to typecheck for the passed thing to actually be a global actor etc... Do we actually ever want to allow passing random actors there? That's not been decided at all so I'm not sure we need the power to do so.
Or #feature() to enable things?
If we really think we need to be able to pass values to these options then I'd be inclined to agree with @Sajjon's idea of spelling it like a macro.
I know it is not a macro in the sense of "expanding code", however it is IMHO by far better to have a macro #feature(.defaultIsolation(.something))
that gets a bit of special treatment that the compiler knows about it enabling the passed feature, rather than invent a completely new thing.
Specifically because:
#something
is understood to be "at compile time"
- this can be expressed using plain, present-day, Swift then; just normal enums which IDEs will just understand and think "oh yeah it's some macro, fine, whatever".
- The expansion could be skipped by the compiler since we know this one evaluates to nothing; IDEs which have no idea about it could attempt to expand, but it would just expand to empty (or some comment)
- we would still apply the limitations where it can appear, just as the current proposal would have to, so that's no different.
Actual rollout thoughts: versioning, deprecating etc
I also worry about the actual use of this in reality then requiring the usual series of #if swift(>=...) ... #endif
around each of those... The macro idea actually works out since we'll just get the suggestions based on the value availability by default...
I would also like the proposal to discuss if we'd ever deprecate an option or mode. Deprecating a value in the settings enum can be painful and may force libs into annoying dances like:
#if swift(>=8.0)
/*.something is default already*/
#else
#feature(.something)`
#endif
So I am thinking if we should in general aim to not deprecate these features even if they become default...? Or I guess use some other warning type other than deprecation, so I could silence those specific kind of warning.
The specific example is a "8.0 supporting library which also supports 7.0 and 6.0" and all those language releases have the setting, but e.g. 8 decided to deprecate it. It would be good to not have to go around my library and in every file make this #if-not dance to avoid warnings...
I was even thinking if we should allow
import feature doesNotExistYet
#feature(.doesNotExistYet)
and cause just a warning... but I think that may be overdoing it... and I guess in those situations we'll have to do the #if dance
around the feature or import... That's probably ok.
Either way, I think this will be valuable overall, but I would like to flesh out how we present this. The longer I thought about this the more I was leaning to a #feature() but the import are also a nice way to express it.