Allow conditional inclusion of elements in array/dictionary literals?

While the parser wouldn’t necessarily need the tailing comma inside a conditional to work, it makes sense to require it as it would fit in with many people’s mental model for how this pre-processor-like feature would work.

1 Like

I agree with Rintaro that we should support #if in all those situations. They will each introduce their own implementation troubles, though, and need to be considered separately because of it. We don’t want an omnibus proposal saying “#if should be accepted anywhere that grammatically operates as a sequence of things”, because we can’t really mark that as ever completed or have an easy way to talk about the impact of the individualized changes. But we can agree to it in principle, and the seperate fixes should be pretty uncontroversial.

4 Likes

Is anything actually using the AST representation of "#if false" code? Are those things important enough to be worth the cost of such an inflexible design? The things sema does with IfConfigDecl are all borderline IMO.

Another design question: what should we do for expressions like this:

let collection = [
#if FLAG
  "foo",
#else
  "foo": 12,
#endif
]

Should this be error?

5 Likes

The textual interface printing is, though that's a new feature. If we want to get rid of it, we'd need to keep the Syntax tree around properly to get the corresponding information. I don't think we're ready for that yet.

+1. I often have long literals of e.g. test cases, and desire to conditionally compile some out. I agree with @rintaro that we should also allow conditional compilation of decl attributes, but that can be future work.

what’s the status of this?

The PR was prepared but it was decided that it had to go to review after all but was never scheduled :frowning:
https://github.com/apple/swift/pull/19347

The core team briefly discussed this, but that discussion we had got cut short and isn't likely to be picked up for a couple of weeks (due to holidays, travel etc). We originally wanted to discuss and get back quickly to the community with some guidance, but since that isn't happening, I think it would be great to see what you all think about these issues.

Here's a short (non-normative!) summary to give a sense of the issue:

Everyone is very +1 on this change, but I (and others) are concerned about the specific patch for this feature, because it adds significant AST complexity to support this (obviously good!) user feature. Swift's design for #if handling is just broken here IMO. While this proposal is one tiny step of progress, we should really support #if in many many many more places, and this proposal/patch doesn't push us in the right direction.

I consider our current design of handling #if blocks to be a failed experiment: the current design is the result of having the parser try to parse the "#if 0" parts of code. This policy is the result of some (now obsolete) goals that I don't want to get into, but don't matter because they are obsolete. The only other reason for parsing the #if 0 code is to make warning messages better in some extremely narrow cases, but that can be done in other ways. I think that @jrose has some goals where the current behavior is useful for textual interface for swift module stability. I don't understand and therefore won't attempt to explain that use, but I suspect that there could be other ways of solving the same problems.

In any case, I think we should go back to the drawing board on the design of #if handling. I think it would be fine to make this a lexer based skipping mechanism, which would allow arbitrary unstructured code. Some other members suggest that we could/should force some amount of structure, which would require parser integration of some sort. As I mentioned before, we didn't have time to really get into it, so the core team doesn't have a recommendation here yet.

I'd love to see the community explore these issues, and design a superset of our current #if directive handling model that will address the user needs (e.g. be able to #if out attributes, arguments, other not-really-structured things) without requiring tremendous AST complexity.

-Chris

13 Likes

While I’m sorry the patch didn’t make it into Swift5, a big +1 to the idea of moving #if processing into the Lexer.
Was this other proposal discussed? It’s all part of my master plan to turn Swift into C.

Why can't this be done with a much more general approach using the emerging const-evaluation?

#if whatever
let x = [8, 9]
#else
let x = []
#endif
let arr = #const( [[1, 2, 3], x, [21, 90]].flatMap { $0 } )

I think when people use if what they use it for is “this is a piece of text I want to copy and paste into the source file depending on some build condition and I’d rather it be automated than have to do it manually for each build case”, so it makes sense that this should be a lexer-level only thing. I get this is venturing into C preprocessor macro territory, but I really don’t see much benefit, and lots of costs to having the parser look through the #ifs.

All these uses of #if sound reasonable to me, but:

It is very useful to documentation tools to be able to understand the AST of any if statements enclosing public symbol declarations for an alternate platform. Workspace’s package documenter relies on it (through SwiftSyntax).

I am less opinionated about #if statements which occur within function bodies or literals, but I still find it useful to be able to see into them when writing tools which transform or validate code.

I would be very sad if SwiftSyntax reduced these code regions to a blanket .uncompiled(String) token.

3 Likes

Likewise for our ongoing formatter work. The fact that #if statements show up as well-structured syntax nodes make it possible to transform the code inside all branches of the conditional in a straightforward way without any awkward special cases.

If inactive branches were reduced to some other raw form, that would make it much harder to handle those conditionals, unless the syntax tree could still somehow represent those conditionals in a uniform way whether they're at the statement level, expression level, or even the attribute level.

1 Like

Nope, nothing we're depending on in our current plans. Syntax tools are the only real consideration I can think of. (Well, that and avoiding unreadable source code.)

Note that syntax tools already ignore inactive sections based on #if swift and #if compiler, but that pretty much can't be helped. After all, different versions of Swift might not support the same syntax.

1 Like

Are the module stability plans to measure things in a cross‐platform way? Or will they produce different results on macOS vs Linux if public symbols are enclosed by #if os(macOS) or #if canImport(ObjectiveC)?

We're getting off track here, but no, there will not be one interface that works across platforms. That wouldn't be possible for a generated interface, since the compiler can't typecheck anything that's in a different #if branch even if it can parse it. See Plan for module stability for all the details.

Sorry, I was mixed up. I was not thinking about ABI stuff. For some reason I thought we were talking about tools for measuring package API differences (i.e. source breaking) for checking semantic versioning.

That is another area were it would be useful to parse #if statements for other platforms. The tool could reasonably scan in several different #if swift modes and combine the result, but it would be much more complicated to need to scan separately on physically different platforms (#if os) and gather those results to combine them. A unified summary would be vastly more convenient.

That is all I will say about it. I think parsing or not of uncompiled #if statements is a matter of how, not whether to implement the pitched uses of #if.

1 Like

Skipping is probably fine for compilation, but compiling isn't the only thing people do with code:
What about refactoring?
Obviously, you don't want to transform only a part of the whole text, and it would be odd if that operation would depend on things like the current target.
So, someone will have to write a parser that examines all if blocks, and this job shouldn't be harder than it has to be.
Also, it would be really nice if the compiler would tell you when you accidentally break a (currently) inactive codepath — even if this diagnosis is only available for simple situations.

2 Likes

I just ran into this. Did this pitch ever make it into code?