Allow conditional inclusion of elements in array/dictionary literals?

Yeah I was wondering that - surely if you have a cross-compilation toolchain, the swift compiler can parse the manifest in the foreign platform's context. The compiler can still run on the host platform (obviously) - it's only the binary products which cannot be executed on that platform.

AFAIK, parsing a Package.swift file does not depend on executing any binary products, so it should be totally do-able.

EDIT: Oh, no, it does execute code - like if you do package.targets.append(...). So I guess it wouldn't work :/

No, the manifests are currently parsed by executing the actual code. We could compile them for different platforms but we'd need to use simulators or emulators to execute them with the current parsing mechanism. We would also need to compile the PackageDescription library for each supported platform. Instead of all that, one possibility is to introduce some declarative APIs to allow you to conditionalize the contents of the manifest file (see @hartbit's post above).

Anyway, I think OP's suggested enhancement is orthogonal and makes sense regardless of what we end up doing for the manifest file.

3 Likes

Why it's only for array/dictional literals? What's the rationale for that?
How about tuples, calling arguments, or other comma separated lists?
Why not #if ... #endif guarded decl attributes?

#if os(macOS)
@objc
#endif
func foobar() {...

or accessors

var value: Int {
  get { ... }
#if swift(>=5.0)
  modify { yield ... }
#else
  set { ... }
#endif
}

I think the community should discuss more deeply about the policy where #if can appear. Currently, conditional block must enclose whole decls/statements (or switch-cases).

8 Likes

There is no rationale. I bought this up because I can already think of a use case for container literals which is making particular tests conditional and I’m sure there are other use cases. Having already looked at the implementation, as it happens extending the scope to include most things that are lists would not be that difficult but I don’t think we should “go to town” just yet and try to implement every conceivable place conditionals could be used unless someone paid to do this is prepared to take it on. It’s just a matter of where to draw the line. A more ambitious scope can still be tackled later.

1 Like

I'm with John on this one. Collection literals are a place where it's very clear what the #if does, whereas allowing it in, say, arbitrary expression positions might be something the community or core team decides it doesn't want because it can lead to harder-to-understand code. Let's not worry about scope-creeping this proposal right away.

3 Likes

Okay. I agreed to move forward on this.

Now I have a question in the design. Sorry for bikeshedding but the following example is legal?

[
#if FLAG
  42
#endif
]

if so, how about:

[
#if FLAG
  42
#endif

#if !FLAG
  12
#endif
]

If you think of #if .. #endif as C like pre-processing directive, this expression ends up with

[
  42

]

or

[

  12
]

So it should be legal. But I think this is error prone for developers because requirement for , relies on the flags or platform conditions.

So, to keep things simple, my proposal here is: "trailing comma is mandatory in #if block in collection literal regardless of the position in the literal"
What do you think?

6 Likes

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:

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
Terms of Service

Privacy Policy

Cookie Policy