digitalmars.D - Corner cases in Primary Type Syntax and its proof-of-concept
- Quirin Schroll (124/124) Oct 11 Here is the [current
- Tim (5/17) Oct 11 A future scope guard could need extra parameters. For example,
Here is the [current draft](https://github.com/Bolpat/DIPs/blob/PrimaryTypeSyntax/DIPs/DIP-2NNN-QFS.md). As I see it, there are three corner cases because there are keywords that by themselves have one meaning and followed by something in parentheses, have a different meaning: - `align` and `align` `(` *AssignExpression* `)` ― Default alignment or specify alignment - `scope` and `scope` `(` *Identifier* `)` ― Storage class/attribute or scope guard - `extern` and `extern` `(` *Tokens* `)` ― Storage class or linkage attribute For any of these, if the former is followed by the kind of *BasicType* syntax the DIP introduces, there is an ambiguity because it could also be the latter: ```d align (X) x = x0; // `align` + type `(X)` (intentionally no “or”) scope (Y) y = y0; // `scope` + type `(Y)` or `scope guard `Y`? extern (Z) z = z0; // `extern` + type `(Z)` (intentionally no “or”) ``` My implementation handles these somewhat differently for various reasons. For `align`, it does nothing special. If a parenthesis follows, it’s treated as specifying alignment. If that isn’t what the programmer intended, but instead a type, it leads to a parse error with certainty (as far as I can tell) because even if `X` turns out semantically as something that’s both an integer *and* a type – which some weird things can (see below) –, it’s treated as the argument to `align` and the declared `x` lacks a type. Because `align` is not a storage class (it’s only an attribute), `align(X)` is no replacement for `auto`. The DIP doesn’t change the existing semantic meaning of anything. Because default alignment can be explicitly specified, there’s a local fix to the problem if `(X)` was meant as a type. Ideally, the compiler could recognize this kind of error and tell the programmer to use `align((X).alignof) (X) x = x0`. Possibly, I can add `align(auto)` as an alternative way to say `align` with no arguments so there’s a simpler and DRY way. For `extern`, it’s similar to `align`, just that the semantic analysis couldn’t distinguish the meaning of the linkage `extern(Z)` form an `extern` attribute applied to a declaration of an object of type `(Z)`. If `Z` was meant to be a type, the declaration lacks a type, which as with `align` is an error as linkage cannot be used instead of `auto`. The programmer either wanted `extern(Z) auto z` or `extern Z z`. Practically speaking, if `Z` is `C`, `D`, `System`, or `Windows`, the parser cannot distinguish what the programmer really wanted. Semantic analysis can if `Z` is not in scope as a type. I did not implement something this, though. For `scope` my implementation recognizes scope guards specifically and it treats `scope` `(`*Tokens*`)` as `scope` `BasicType` unless it’s in statement scope and *Tokens* is exactly `exit`, `success`, or `failure`. In the monthly meeting today, the latter was seen as contentious as it does not allow for adding any new scope guards. D’s syntax of linkages and scope guards was designed to flexible to allow for either implementation-defined nice-to-read linkages (e.g. `C++` and `Objective-C` in DMD) or the addition of nice-to-read scope guards. Walter convinced me that the implementation limits the flexibility of future scope guards. I suggested that if *Tokens* is exactly one identifier (or one single token), it could be a scope guard (and be an error if it’s not a valid scope guard), otherwise a `BasicType` (and be an error if it isn’t). This is for two reasons: No-one on the meeting could imagine a scope guard that cannot be a single identifier (or keyword if need be) and in that case, if a type clashed with a scope guard, removing the parentheses around the token would always work as the types that require parentheses to express with this DIP are always longer than one token. There are these solutions: - Convince Walter that multi-token scope guards are never needed. - Change the implementation and disallow `scope` `(`*Tokens*`)` unless it forms a valid scope guard (which currently means *Tokens* must be `exit`, `success`, or `failure`, but it could be more in the future). The second option is easy to implement in theory, but in practice wouldn’t allow the following, reasonable declaration that is specifically enabled by this DIP: ```d int x; void f() { scope (ref int delegate()) dg = () => x; } ``` If it were not allowed, then it becomes a *reasonable* error to make in the context of the goal of this DIP, and DMD should point out how to correctly declare `dg`. (This is similar to how DMD diagnoses C array declarations: It recognizes them and tells the programmer how to declare the array in D style, and it should do the same with `dg`.) The only question is, *what* should DMD suggest? Or what would you suggest a programmer to do who’s asking this as a forum question? To be honest, I don’t know. That’s why I’m asking. - "Use `scope dg`" is infers a different type than the one specified (one with attributes). - "Remove `scope` and let the compiler infer it." If `scope` would be inferred, nothing changes, but if it wasn’t, maybe something changes meaning down the road. - "Use `scope 0 (ref int delegate()) dg`." This has the problem that adding a UDA can change behavior. An empty UDA sequence ` ()` would work, but empty UDA sequences are expressly not allowed. - "Use `scope typeof((ref int delegate()).init) dg`" would work, but is ugly and hacky. IMO, just close the door on *some* multi-token scope guards, namely those that would otherwise be types. It is not likely to be an issue. As with linkage, `Objective-C` is not a type and `C++` is also never a type. --- ```d // currently working D code: struct Weird { static int opIndex() safe => 8; } void main() safe { Weird[] weirds; align(Weird[]) int x; static assert(x.alignof == 8); // passes } ```
Oct 11
On Friday, 11 October 2024 at 18:19:01 UTC, Quirin Schroll wrote:Walter convinced me that the implementation limits the flexibility of future scope guards. I suggested that if *Tokens* is exactly one identifier (or one single token), it could be a scope guard (and be an error if it’s not a valid scope guard), otherwise a `BasicType` (and be an error if it isn’t). This is for two reasons: No-one on the meeting could imagine a scope guard that cannot be a single identifier (or keyword if need be) and in that case, if a type clashed with a scope guard, removing the parentheses around the token would always work as the types that require parentheses to express with this DIP are always longer than one token.A future scope guard could need extra parameters. For example, here was a suggestion for `scope (failure, ExceptionSpecification)`: https://forum.dlang.org/thread/mailman.723.1427369014.3111.digitalmars-d puremagic.com#post-mailman.723.1427369014.3111.digitalmars-d:40puremagic.com
Oct 11