From af50df4ffd866c18fbc5bb9c69dc39eb806fcf8d Mon Sep 17 00:00:00 2001 From: "Q. F. Schroll" Date: Wed, 4 Nov 2020 23:06:49 +0100 Subject: [PATCH 01/27] Rename grammar entities for more descriptive names --- src/dmd/parse.d | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dmd/parse.d b/src/dmd/parse.d index 002fe314b785..a66df0911368 100644 --- a/src/dmd/parse.d +++ b/src/dmd/parse.d @@ -3992,9 +3992,9 @@ final class Parser(AST) : Lexer * t function * t delegate */ - private AST.Type parseBasicType2(AST.Type t) + private AST.Type parseTypeSuffixes(AST.Type t) { - //printf("parseBasicType2()\n"); + //printf("parseTypeSuffixes()\n"); while (1) { switch (token.value) @@ -4099,7 +4099,7 @@ final class Parser(AST) : Lexer bool* pdisable = null, AST.Expressions** pudas = null) { //printf("parseDeclarator(tpl = %p)\n", tpl); - t = parseBasicType2(t); + t = parseTypeSuffixes(t); AST.Type ts; switch (token.value) { @@ -4755,7 +4755,7 @@ final class Parser(AST) : Lexer else { ts = parseBasicType(); - ts = parseBasicType2(ts); + ts = parseTypeSuffixes(ts); } } } @@ -4995,7 +4995,7 @@ final class Parser(AST) : Lexer // function type (parameters) { statements... } // delegate type (parameters) { statements... } tret = parseBasicType(); - tret = parseBasicType2(tret); // function return type + tret = parseTypeSuffixes(tret); // function return type } if (token.value == TOK.leftParentheses) @@ -9164,7 +9164,7 @@ final class Parser(AST) : Lexer const stc = parseTypeCtor(); auto t = parseBasicType(true); - t = parseBasicType2(t); + t = parseTypeSuffixes(t); t = t.addSTC(stc); if (t.ty == AST.Taarray) { From a4957d78a56d80cecd16ed6f30498086462a17b2 Mon Sep 17 00:00:00 2001 From: "Quirin F. Schroll" Date: Wed, 17 May 2023 18:50:01 +0200 Subject: [PATCH 02/27] Implement BasicType as primary type (expression) --- compiler/src/dmd/parse.d | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 10f42a0d17cd..25b87219d372 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3462,6 +3462,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Type parseType(Identifier* pident = null, AST.TemplateParameters** ptpl = null) { + // Handle `ref` TypeCtors(opt) BasicType CallableSuffix TypeSuffixes(opt) + const bool isRef = token.value == TOK.ref_; + if (isRef) nextToken(); + /* Take care of the storage class prefixes that * serve as type attributes: * const type @@ -3516,6 +3520,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Type t; t = parseBasicType(); + if (isRef) + { + t = t.addSTC(stc); + t = parseTypeSuffixes(t, true); + return t; + } int alt = 0; t = parseDeclarator(t, alt, pident, ptpl); @@ -3728,6 +3738,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer check(TOK.rightParenthesis); break; + case TOK.leftParenthesis: + // (type) + t = parseType(); + check(TOK.rightParenthesis); + break; + default: error("basic type expected, not `%s`", token.toChars()); if (token.value == TOK.else_) @@ -3888,8 +3904,13 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * See_Also: * https://dlang.org/spec/declaration.html#TypeSuffixes */ - private AST.Type parseTypeSuffixes(AST.Type t) + private AST.Type parseTypeSuffixes(AST.Type t, bool isRefCallable = false) { + // ref TypeCtors(opt) BasicType CallableSuffix TypeSuffixes(opt) + if (isRefCallable && token.value != TOK.delegate_ && token.value != TOK.function_) + { + error("`ref` is only valid for `function` and `delegate` types"); + } //printf("parseTypeSuffixes()\n"); while (1) { @@ -3948,14 +3969,23 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.function_: { // Handle delegate declaration: - // t delegate(parameter list) nothrow pure - // t function(parameter list) nothrow pure + // ref(opt) type delegate(parameter list) nothrow pure + // ref(opt) type function(parameter list) nothrow pure const save = token.value; nextToken(); auto parameterList = parseParameterList(null); - StorageClass stc = parsePostfix(STC.undefined_, null); + StorageClass stc; + if (isRefCallable) + { + stc = parsePostfix(STC.ref_, null); + isRefCallable = false; // ref applies to the innermost + } + else + { + stc = parsePostfix(STC.undefined_, null); + } auto tf = new AST.TypeFunction(parameterList, t, linkage, stc); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild | STC.return_)) { From 9a456edccd9d76e076ec728c1064e61c05e8e46c Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Wed, 17 May 2023 19:07:42 +0200 Subject: [PATCH 03/27] Fix issue 2753 - add good case tests --- compiler/src/dmd/parse.d | 9 +++--- compiler/test/runnable/reffunctype.d | 48 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 compiler/test/runnable/reffunctype.d diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 25b87219d372..e3accab9dfc7 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3523,12 +3523,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (isRef) { t = t.addSTC(stc); - t = parseTypeSuffixes(t, true); - return t; + stc = STC.undefined_; } int alt = 0; - t = parseDeclarator(t, alt, pident, ptpl); + t = parseDeclarator(t, alt, pident, ptpl, 0, null, null, isRef); checkCstyleTypeSyntax(typeLoc, t, alt, pident ? *pident : null); t = t.addSTC(stc); @@ -4022,10 +4021,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ private AST.Type parseDeclarator(AST.Type t, ref int palt, Identifier* pident, AST.TemplateParameters** tpl = null, StorageClass storageClass = 0, - bool* pdisable = null, AST.Expressions** pudas = null) + bool* pdisable = null, AST.Expressions** pudas = null, bool isRefCallable = false) { //printf("parseDeclarator(tpl = %p)\n", tpl); - t = parseTypeSuffixes(t); + t = parseTypeSuffixes(t, isRefCallable); AST.Type ts; switch (token.value) { diff --git a/compiler/test/runnable/reffunctype.d b/compiler/test/runnable/reffunctype.d new file mode 100644 index 000000000000..1d296dcac29c --- /dev/null +++ b/compiler/test/runnable/reffunctype.d @@ -0,0 +1,48 @@ +// REQUIRED_ARGS: -unittest -main + +int i; + +void hof1((ref int function()) fptr) { fptr()++; } +void hof2(ref (int function()) fptr) { fptr = ref () => i; } +void hof3(ref (ref int function())[] fptrs) +{ + static assert(__traits(isRef, fptrs)); + fptrs[0]() = 1; +} + +ref int h () => i; +ref int function() fptr = &h; + +unittest +{ + hof1(&h); + hof1(ref() => i); + assert(i == 2); + + ref int function() fp = &h; + fp()++; + assert(i == 3); + + fptr()++; + assert(i == 4); +} + +alias Func = ref int function(); +static assert(is(Func == typeof(&h))); + +struct S +{ + int i; + ref int get() => i; +} + +unittest +{ + S s; + ref int delegate() d = &s.get; + d()++; + assert(s.i == 1); +} + +alias Del = ref int delegate(); +static assert(is(Del == typeof(&S().get))); From 3d655e88972fc65eac0dc5e7d023bec037d702f6 Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Tue, 23 May 2023 20:17:02 +0200 Subject: [PATCH 04/27] Finished implementation --- compiler/src/dmd/hdrgen.d | 10 +++++++--- compiler/src/dmd/parse.d | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index a159c2f15617..ce0023f920f7 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -3646,6 +3646,8 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, OutBu buf.write("static "); if (t.next) { + if (t.isref) + buf.write("(ref "); typeToBuffer(t.next, null, buf, hgs); if (ident) buf.writeByte(' '); @@ -3663,13 +3665,15 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, OutBu MODtoBuffer(buf, t.mod); } - void dg(string str) + void writeAttribute(string str) { + if (str == "ref") return; // 'ref' is handeld above buf.writeByte(' '); buf.writestring(str); } - t.attributesApply(&dg); - + t.attributesApply(&writeAttribute); + if (t.isref) + buf.writeByte(')'); t.inuse--; } diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index e3accab9dfc7..ef4a5b993747 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -422,6 +422,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.class_: case TOK.interface_: case TOK.traits: + case TOK.leftParenthesis: Ldeclaration: a = parseDeclarations(false, pAttrs, pAttrs.comment); if (a && a.length) @@ -3739,6 +3740,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.leftParenthesis: // (type) + nextToken(); t = parseType(); check(TOK.rightParenthesis); break; @@ -3907,9 +3909,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { // ref TypeCtors(opt) BasicType CallableSuffix TypeSuffixes(opt) if (isRefCallable && token.value != TOK.delegate_ && token.value != TOK.function_) - { error("`ref` is only valid for `function` and `delegate` types"); - } + //printf("parseTypeSuffixes()\n"); while (1) { @@ -5794,7 +5795,6 @@ LagainStc: case TOK.true_: case TOK.false_: case TOK.string_: - case TOK.leftParenthesis: case TOK.cast_: case TOK.mul: case TOK.min: @@ -5997,6 +5997,12 @@ LagainStc: s = new AST.ScopeStatement(loc, s, token.loc); break; } + case TOK.leftParenthesis: + { + if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) + goto Ldeclaration; + goto Lexp; + } case TOK.mixin_: { if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) @@ -7344,12 +7350,31 @@ LagainStc: t = peek(t); if (t.value != TOK.leftParenthesis) goto Lfalse; + goto case; + + case TOK.leftParenthesis: + // (type) t = peek(t); - if (!isDeclaration(t, NeedDeclaratorId.no, TOK.rightParenthesis, &t)) - { + const bool isRef = t.value == TOK.ref_; + if (isRef) t = peek(t); + while (t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) + t = peek(t); + if (!isBasicType(&t)) + goto Lfalse; + if (isRef && t.value != TOK.function_ && t.value != TOK.delegate_) + goto Lfalse; + int haveId = 1; + int haveTpl = 0; + if (!isDeclarator(&t, &haveId, &haveTpl, TOK.rightParenthesis)) + goto Lfalse; + if (t.value != TOK.rightParenthesis) goto Lfalse; - } t = peek(t); + + // `(x) { }` in a template argument list is a problem; `(x)` is not a type, but read as if. + if (t.value == TOK.leftCurly) + goto Lfalse; + break; default: From 6cb72d364f7e29bf63ba2446ace907d6f61f2d3c Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Tue, 23 May 2023 22:18:47 +0200 Subject: [PATCH 05/27] Fix demo --- compiler/test/runnable/reffunctype.d | 110 +++++++++++++++++++++------ 1 file changed, 85 insertions(+), 25 deletions(-) diff --git a/compiler/test/runnable/reffunctype.d b/compiler/test/runnable/reffunctype.d index 1d296dcac29c..48911b56b5d1 100644 --- a/compiler/test/runnable/reffunctype.d +++ b/compiler/test/runnable/reffunctype.d @@ -1,48 +1,108 @@ // REQUIRED_ARGS: -unittest -main -int i; +// `a` takes its parameter by value; the parameter returns by reference. +// `b` takes its parameter by reference; the parameter returns by value. +// The parameter storage class has priority over the value category of the parameter’s return type. +void a( ref (int function()) ) { } +void b((ref int function()) ) { } -void hof1((ref int function()) fptr) { fptr()++; } -void hof2(ref (int function()) fptr) { fptr = ref () => i; } -void hof3(ref (ref int function())[] fptrs) +// `c` is `a` without clarifiying parentheses. +void c( ref int function() ) { } + +static assert(!is( typeof(&a) == typeof(&b) )); +static assert( is( typeof(&a) == typeof(&c) )); + +// `x` returns by reference; the return type is a function that returns an `int` by value. +// `y` returns by vale; the return type is a function that returns an `int` by reference. +// The value category of the declared function has priority over the value category of the return type. + ref (int function()) x() { static typeof(return) fp = null; return fp; } +(ref int function()) y() => null; + +// `z` is `x` without clarifiying parentheses. + ref int function() z() => x(); + +static assert(!is( typeof(&x) == typeof(&y) )); +static assert( is( typeof(&x) == typeof(&z) )); + +@safe unittest +{ + static int i = 0; + // Congruence between function declaration and function type. + ref int funcName() @safe => i; + (ref int delegate() @safe) fptr = &funcName; +} + +// Combination of ref return and binding parameters by reference +// as well as returning by reference and by reference returning function type. +ref (ref int function() @safe) hof(ref (ref int function() @safe)[] fptrs) @safe { static assert(__traits(isRef, fptrs)); fptrs[0]() = 1; + (ref int function() @safe)* result = &fptrs[0]; + fptrs = []; + return *result; } -ref int h () => i; -ref int function() fptr = &h; - -unittest +@safe unittest { - hof1(&h); - hof1(ref() => i); - assert(i == 2); + static int i = 0; + static ref int f() => i; + static assert(is(typeof(&f) == ref int function() nothrow @nogc @safe)); - ref int function() fp = &h; - fp()++; + (ref int function() @safe)[] fps = [ &f, &f ]; + auto fpp = &(hof(fps)); + assert(fps.length == 0); + assert(*fpp == &f); + assert(i == 1); + static assert(is(typeof(fpp) == (ref int function() @safe)*)); + int* p = &((*fpp)()); + *p = 2; + assert(i == 2); + (*fpp)()++; assert(i == 3); - - fptr()++; - assert(i == 4); } -alias Func = ref int function(); -static assert(is(Func == typeof(&h))); - struct S { int i; - ref int get() => i; + ref int get() @safe return => i; } -unittest +@safe unittest { S s; - ref int delegate() d = &s.get; - d()++; + (ref int delegate() return @safe) dg = &s.get; + dg() = 1; assert(s.i == 1); } -alias Del = ref int delegate(); -static assert(is(Del == typeof(&S().get))); +static assert(is(typeof(&S().get) == ref int delegate() @safe return)); + +@safe unittest +{ + static int x = 1; + assert(x == 1); + auto f = function ref int() => x; + static assert(is( typeof(f) : ref int function() @safe )); + f() = 2; + assert(x == 2); + takesFP(f); + assert(x == 3); + + auto g = cast(ref int function()) f; +} + +ref (ref int function() @safe) returnsFP() @safe { static (ref int function() @safe) fp = null; return fp; } +void takesFP((ref int function() @safe) fp) @safe { fp() = 3; } + +void takesFPFP(typeof(&returnsFP) function( typeof(&returnsFP) )) { } + +// pretty print and actual D syntax coincide even in convoluted cases +static assert( typeof(&takesFPFP).stringof == "void function((ref (ref int function() @safe) function() @safe) function((ref (ref int function() @safe) function() @safe)))"); +static assert(is(typeof(&takesFPFP) == void function((ref (ref int function() @safe) function() @safe) function((ref (ref int function() @safe) function() @safe))) )); + +// as an artifact of the type grammar, these should hold: +static assert(is( (int) == int )); +static assert(is( (const int) == const(int) )); +static assert(is( (const shared int) == shared(const(int)) )); +static assert(is( (const shared int) == shared(const int ) )); From 790c08cb7da690ed1a3bb3267ab0ce6b33f3bd5c Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Tue, 23 May 2023 22:53:20 +0200 Subject: [PATCH 06/27] Adapt test cases to new pretty print syntax + additional errors --- compiler/test/fail_compilation/diag_template_alias.d | 1 + compiler/test/fail_compilation/diag_template_this.d | 1 + compiler/test/fail_compilation/fail21243.d | 3 +-- compiler/test/fail_compilation/fail270.d | 2 +- compiler/test/fail_compilation/issue16020.d | 3 ++- compiler/test/fail_compilation/template_decl.d | 1 + compiler/test/fail_compilation/test21546.d | 8 ++++---- compiler/test/runnable/declaration.d | 2 +- compiler/test/runnable/testscope2.d | 8 ++++---- 9 files changed, 16 insertions(+), 13 deletions(-) diff --git a/compiler/test/fail_compilation/diag_template_alias.d b/compiler/test/fail_compilation/diag_template_alias.d index 151bb426574d..3b396dbb1072 100644 --- a/compiler/test/fail_compilation/diag_template_alias.d +++ b/compiler/test/fail_compilation/diag_template_alias.d @@ -4,6 +4,7 @@ TEST_OUTPUT: fail_compilation/diag_template_alias.d(1): Error: identifier expected for template `alias` parameter fail_compilation/diag_template_alias.d(1): Error: found `alias` when expecting `(` fail_compilation/diag_template_alias.d(1): Error: semicolon expected following function declaration, not `(` +fail_compilation/diag_template_alias.d(1): Error: basic type expected, not `)` fail_compilation/diag_template_alias.d(1): Error: declaration expected, not `(` --- */ diff --git a/compiler/test/fail_compilation/diag_template_this.d b/compiler/test/fail_compilation/diag_template_this.d index 25de03ce19ed..cc0884847910 100644 --- a/compiler/test/fail_compilation/diag_template_this.d +++ b/compiler/test/fail_compilation/diag_template_this.d @@ -4,6 +4,7 @@ TEST_OUTPUT: fail_compilation/diag_template_this.d(1): Error: identifier expected for template `this` parameter fail_compilation/diag_template_this.d(1): Error: found `this` when expecting `(` fail_compilation/diag_template_this.d(1): Error: semicolon expected following function declaration, not `(` +fail_compilation/diag_template_this.d(1): Error: basic type expected, not `)` fail_compilation/diag_template_this.d(1): Error: declaration expected, not `(` --- */ diff --git a/compiler/test/fail_compilation/fail21243.d b/compiler/test/fail_compilation/fail21243.d index 2e170d096c59..b52cad863aeb 100644 --- a/compiler/test/fail_compilation/fail21243.d +++ b/compiler/test/fail_compilation/fail21243.d @@ -5,8 +5,7 @@ fail_compilation/fail21243.d(16): Error: semicolon expected following auto decla fail_compilation/fail21243.d(16): Error: semicolon needed to end declaration of `x` instead of `)` fail_compilation/fail21243.d(16): Error: declaration expected, not `)` fail_compilation/fail21243.d(17): Error: `auto` can only be used as part of `auto ref` for function literal return values -fail_compilation/fail21243.d(18): Error: basic type expected, not `(` -fail_compilation/fail21243.d(18): Error: function declaration without return type. (Note that constructors are always named `this`) +fail_compilation/fail21243.d(18): Error: unexpected identifier `x` in declarator fail_compilation/fail21243.d(18): Deprecation: storage class `auto` has no effect in type aliases fail_compilation/fail21243.d(18): Error: semicolon expected to close `alias` declaration, not `=>` fail_compilation/fail21243.d(18): Error: declaration expected, not `=>` diff --git a/compiler/test/fail_compilation/fail270.d b/compiler/test/fail_compilation/fail270.d index 188fab87ac05..04cf058483e2 100644 --- a/compiler/test/fail_compilation/fail270.d +++ b/compiler/test/fail_compilation/fail270.d @@ -1,7 +1,7 @@ /* TEST_OUTPUT: --- -fail_compilation/fail270.d(12): Error: string slice `[1 .. 0]` is out of bounds +fail_compilation/fail270.d(12): Error: slice `[1..0]` is out of range of `[0..0]` fail_compilation/fail270.d(12): Error: mixin `fail270.Tuple!int.Tuple.Tuple!()` error instantiating fail_compilation/fail270.d(14): Error: mixin `fail270.Tuple!int` error instantiating --- diff --git a/compiler/test/fail_compilation/issue16020.d b/compiler/test/fail_compilation/issue16020.d index fe4ad78f1acc..4b628017bd26 100644 --- a/compiler/test/fail_compilation/issue16020.d +++ b/compiler/test/fail_compilation/issue16020.d @@ -3,7 +3,8 @@ TEST_OUTPUT: --- fail_compilation/issue16020.d(12): Error: user-defined attributes not allowed for `alias` declarations fail_compilation/issue16020.d(13): Error: semicolon expected to close `alias` declaration, not `(` -fail_compilation/issue16020.d(13): Error: declaration expected, not `(` +fail_compilation/issue16020.d(13): Error: unexpected identifier `t` in declarator +fail_compilation/issue16020.d(13): Error: no identifier for declarator `T` --- */ module issue16020; diff --git a/compiler/test/fail_compilation/template_decl.d b/compiler/test/fail_compilation/template_decl.d index d986dd65065f..00fc6103338f 100644 --- a/compiler/test/fail_compilation/template_decl.d +++ b/compiler/test/fail_compilation/template_decl.d @@ -2,6 +2,7 @@ TEST_OUTPUT: --- fail_compilation/template_decl.d(8): Error: `{` expected after template parameter list, not `(` +fail_compilation/template_decl.d(8): Error: basic type expected, not `)` fail_compilation/template_decl.d(8): Error: declaration expected, not `(` --- */ diff --git a/compiler/test/fail_compilation/test21546.d b/compiler/test/fail_compilation/test21546.d index 22565e4a8a2e..a9f09ceeda72 100644 --- a/compiler/test/fail_compilation/test21546.d +++ b/compiler/test/fail_compilation/test21546.d @@ -3,10 +3,10 @@ fail_compilation/test21546.d(113): Error: cannot implicitly convert expression `pc` of type `const(int)* delegate() return` to `int* delegate() return` fail_compilation/test21546.d(114): Error: cannot implicitly convert expression `pc` of type `const(int)* delegate() return` to `immutable(int)* delegate() return` fail_compilation/test21546.d(115): Error: cannot implicitly convert expression `pi` of type `immutable(int)* delegate() return` to `int* delegate() return` -fail_compilation/test21546.d(213): Error: cannot implicitly convert expression `dc` of type `const(int) delegate() ref return` to `int delegate() ref return` -fail_compilation/test21546.d(214): Error: cannot implicitly convert expression `dc` of type `const(int) delegate() ref return` to `immutable(int) delegate() ref return` -fail_compilation/test21546.d(215): Error: cannot implicitly convert expression `di` of type `immutable(int) delegate() ref return` to `int delegate() ref return` -fail_compilation/test21546.d(305): Error: cannot implicitly convert expression `[dgi]` of type `immutable(int) delegate() ref return[]` to `int delegate() ref return[]` +fail_compilation/test21546.d(213): Error: cannot implicitly convert expression `dc` of type `(ref const(int) delegate() return)` to `(ref int delegate() return)` +fail_compilation/test21546.d(214): Error: cannot implicitly convert expression `dc` of type `(ref const(int) delegate() return)` to `(ref immutable(int) delegate() return)` +fail_compilation/test21546.d(215): Error: cannot implicitly convert expression `di` of type `(ref immutable(int) delegate() return)` to `(ref int delegate() return)` +fail_compilation/test21546.d(305): Error: cannot implicitly convert expression `[dgi]` of type `(ref immutable(int) delegate() return)[]` to `(ref int delegate() return)[]` --- */ // https://issues.dlang.org/show_bug.cgi?id=21546 diff --git a/compiler/test/runnable/declaration.d b/compiler/test/runnable/declaration.d index 5be76fac6d74..26299803906c 100644 --- a/compiler/test/runnable/declaration.d +++ b/compiler/test/runnable/declaration.d @@ -56,7 +56,7 @@ void test6905() auto ref baz1() { static int n; return n; } auto ref baz2() { int n; return n; } auto ref baz3() { return 1; } - static assert(typeof(&baz1).stringof == "int delegate() nothrow @nogc ref @safe"); + static assert(typeof(&baz1).stringof == "(ref int delegate() nothrow @nogc @safe)"); static assert(typeof(&baz2).stringof == "int delegate() pure nothrow @nogc @safe"); static assert(typeof(&baz3).stringof == "int delegate() pure nothrow @nogc @safe"); } diff --git a/compiler/test/runnable/testscope2.d b/compiler/test/runnable/testscope2.d index a1164dc4beb9..e41789cadd1f 100644 --- a/compiler/test/runnable/testscope2.d +++ b/compiler/test/runnable/testscope2.d @@ -2,10 +2,10 @@ /* TEST_OUTPUT: --- -foo1 ulong function(return ref int* delegate() return p) ref return -foo2 int function(return ref int delegate() p) ref -foo3 int function(ref inout(int*) p) ref -foo4 int function(return ref inout(int*) p) ref +foo1 (ref ulong function(return ref int* delegate() return p) return) +foo2 (ref int function(return ref int delegate() p)) +foo3 (ref int function(ref inout(int*) p)) +foo4 (ref int function(return ref inout(int*) p)) --- */ From 8af393a0715a83e5f3f1bbefb1f6c436db1e6261 Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Tue, 23 May 2023 23:53:26 +0200 Subject: [PATCH 07/27] Allow (type).ident for type 'ref T function()' --- compiler/src/dmd/parse.d | 6 +++--- compiler/test/runnable/reffunctype.d | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index ef4a5b993747..18d5b2ef6edb 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -8516,9 +8516,9 @@ LagainStc: goto case_delegate; } } - nextToken(); - error("found `%s` when expecting function literal following `ref`", token.toChars()); - goto Lerr; + t = parseType(); + e = new AST.TypeExp(loc, t); + break; } case TOK.leftParenthesis: { diff --git a/compiler/test/runnable/reffunctype.d b/compiler/test/runnable/reffunctype.d index 48911b56b5d1..7375ec61e648 100644 --- a/compiler/test/runnable/reffunctype.d +++ b/compiler/test/runnable/reffunctype.d @@ -83,7 +83,8 @@ static assert(is(typeof(&S().get) == ref int delegate() @safe return)); static int x = 1; assert(x == 1); auto f = function ref int() => x; - static assert(is( typeof(f) : ref int function() @safe )); + static assert( is( typeof(f) : ref const int function() @safe )); + static assert(!is( typeof(f) : ref immutable int function() @safe )); f() = 2; assert(x == 2); takesFP(f); @@ -100,6 +101,7 @@ void takesFPFP(typeof(&returnsFP) function( typeof(&returnsFP) )) { } // pretty print and actual D syntax coincide even in convoluted cases static assert( typeof(&takesFPFP).stringof == "void function((ref (ref int function() @safe) function() @safe) function((ref (ref int function() @safe) function() @safe)))"); static assert(is(typeof(&takesFPFP) == void function((ref (ref int function() @safe) function() @safe) function((ref (ref int function() @safe) function() @safe))) )); +static assert((ref int function()).stringof == "(ref int function())"); // as an artifact of the type grammar, these should hold: static assert(is( (int) == int )); From a279f3c4278c60501fd3bbc937d61190ee25b809 Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Fri, 20 Oct 2023 14:43:40 +0200 Subject: [PATCH 08/27] fix fail compilation --- .../fail_compilation/diag_template_alias.d | 1 - .../fail_compilation/diag_template_this.d | 1 - compiler/test/fail_compilation/fail21243.d | 22 ++++++++++--------- compiler/test/fail_compilation/issue16020.d | 8 +++---- .../test/fail_compilation/template_decl.d | 1 - 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/compiler/test/fail_compilation/diag_template_alias.d b/compiler/test/fail_compilation/diag_template_alias.d index 3b396dbb1072..151bb426574d 100644 --- a/compiler/test/fail_compilation/diag_template_alias.d +++ b/compiler/test/fail_compilation/diag_template_alias.d @@ -4,7 +4,6 @@ TEST_OUTPUT: fail_compilation/diag_template_alias.d(1): Error: identifier expected for template `alias` parameter fail_compilation/diag_template_alias.d(1): Error: found `alias` when expecting `(` fail_compilation/diag_template_alias.d(1): Error: semicolon expected following function declaration, not `(` -fail_compilation/diag_template_alias.d(1): Error: basic type expected, not `)` fail_compilation/diag_template_alias.d(1): Error: declaration expected, not `(` --- */ diff --git a/compiler/test/fail_compilation/diag_template_this.d b/compiler/test/fail_compilation/diag_template_this.d index cc0884847910..25de03ce19ed 100644 --- a/compiler/test/fail_compilation/diag_template_this.d +++ b/compiler/test/fail_compilation/diag_template_this.d @@ -4,7 +4,6 @@ TEST_OUTPUT: fail_compilation/diag_template_this.d(1): Error: identifier expected for template `this` parameter fail_compilation/diag_template_this.d(1): Error: found `this` when expecting `(` fail_compilation/diag_template_this.d(1): Error: semicolon expected following function declaration, not `(` -fail_compilation/diag_template_this.d(1): Error: basic type expected, not `)` fail_compilation/diag_template_this.d(1): Error: declaration expected, not `(` --- */ diff --git a/compiler/test/fail_compilation/fail21243.d b/compiler/test/fail_compilation/fail21243.d index b52cad863aeb..1479be055ec5 100644 --- a/compiler/test/fail_compilation/fail21243.d +++ b/compiler/test/fail_compilation/fail21243.d @@ -1,17 +1,19 @@ /+ TEST_OUTPUT: --- -fail_compilation/fail21243.d(16): Error: found `(` when expecting `ref` and function literal following `auto` -fail_compilation/fail21243.d(16): Error: semicolon expected following auto declaration, not `int` -fail_compilation/fail21243.d(16): Error: semicolon needed to end declaration of `x` instead of `)` -fail_compilation/fail21243.d(16): Error: declaration expected, not `)` -fail_compilation/fail21243.d(17): Error: `auto` can only be used as part of `auto ref` for function literal return values -fail_compilation/fail21243.d(18): Error: unexpected identifier `x` in declarator -fail_compilation/fail21243.d(18): Deprecation: storage class `auto` has no effect in type aliases -fail_compilation/fail21243.d(18): Error: semicolon expected to close `alias` declaration, not `=>` -fail_compilation/fail21243.d(18): Error: declaration expected, not `=>` -fail_compilation/fail21243.d(19): Error: `auto` can only be used as part of `auto ref` for function literal return values +fail_compilation/fail21243.d(1): Error: found `(` when expecting `ref` and function literal following `auto` +fail_compilation/fail21243.d(1): Error: semicolon expected following auto declaration, not `int` +fail_compilation/fail21243.d(1): Error: semicolon needed to end declaration of `x` instead of `)` +fail_compilation/fail21243.d(1): Error: declaration expected, not `)` +fail_compilation/fail21243.d(2): Error: `auto` can only be used as part of `auto ref` for function literal return values +fail_compilation/fail21243.d(3): Error: basic type expected, not `(` +fail_compilation/fail21243.d(3): Error: function declaration without return type. (Note that constructors are always named `this`) +fail_compilation/fail21243.d(3): Deprecation: storage class `auto` has no effect in type aliases +fail_compilation/fail21243.d(3): Error: semicolon expected to close `alias` declaration, not `=>` +fail_compilation/fail21243.d(3): Error: declaration expected, not `=>` +fail_compilation/fail21243.d(4): Error: `auto` can only be used as part of `auto ref` for function literal return values --- +/ +#line 1 auto a = auto (int x) => x; auto b = function auto (int x) { return x; }; alias c = auto (int x) => x; diff --git a/compiler/test/fail_compilation/issue16020.d b/compiler/test/fail_compilation/issue16020.d index 4b628017bd26..a27ff6b3795e 100644 --- a/compiler/test/fail_compilation/issue16020.d +++ b/compiler/test/fail_compilation/issue16020.d @@ -1,14 +1,14 @@ /* TEST_OUTPUT: --- -fail_compilation/issue16020.d(12): Error: user-defined attributes not allowed for `alias` declarations -fail_compilation/issue16020.d(13): Error: semicolon expected to close `alias` declaration, not `(` -fail_compilation/issue16020.d(13): Error: unexpected identifier `t` in declarator -fail_compilation/issue16020.d(13): Error: no identifier for declarator `T` +fail_compilation/issue16020.d(1): Error: user-defined attributes not allowed for `alias` declarations +fail_compilation/issue16020.d(2): Error: semicolon expected to close `alias` declaration, not `(` +fail_compilation/issue16020.d(2): Error: declaration expected, not `(` --- */ module issue16020; struct UDA{} +#line 1 alias Fun = @UDA void(); alias FunTemplate = void(T)(T t); diff --git a/compiler/test/fail_compilation/template_decl.d b/compiler/test/fail_compilation/template_decl.d index 00fc6103338f..d986dd65065f 100644 --- a/compiler/test/fail_compilation/template_decl.d +++ b/compiler/test/fail_compilation/template_decl.d @@ -2,7 +2,6 @@ TEST_OUTPUT: --- fail_compilation/template_decl.d(8): Error: `{` expected after template parameter list, not `(` -fail_compilation/template_decl.d(8): Error: basic type expected, not `)` fail_compilation/template_decl.d(8): Error: declaration expected, not `(` --- */ From ac048a0c56b53c3cc68e97a85c697d4b82ef3f7c Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Wed, 20 Dec 2023 16:07:14 +0100 Subject: [PATCH 09/27] Better error message --- compiler/src/dmd/dsymbolsem.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index df0a9a536f49..8deb1dc21ced 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -1046,7 +1046,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor if ((dsym.storage_class & (STC.ref_ | STC.parameter | STC.foreach_ | STC.temp | STC.result)) == STC.ref_ && dsym.ident != Id.This) { - .error(dsym.loc, "%s `%s` - only parameters, functions and `foreach` declarations can be `ref`", dsym.kind, dsym.toPrettyChars); + .error(dsym.loc, "%s `%s` - only parameters, functions and `foreach` declarations can be `ref`; use parentheses for a function pointer or delegate type with `ref` return", dsym.kind, dsym.toPrettyChars); } if (dsym.type.hasWild()) From dab04e45836639f21ce17baf84c05aeabcc205ab Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Wed, 20 Dec 2023 16:08:57 +0100 Subject: [PATCH 10/27] Generalize string representation of types that require parentheses --- compiler/src/dmd/hdrgen.d | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index 540dc5ee327b..16aad7f8af06 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -3765,8 +3765,18 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, ref O return; } t.inuse++; + bool parenWritten = false; + void openParenthesis() + { + if (!parenWritten) + { + buf.writeByte('('); + parenWritten = true; + } + } if (t.linkage > LINK.d && hgs.ddoc != 1 && !hgs.hdrgen) { + openParenthesis(); linkageToBuffer(buf, t.linkage); buf.writeByte(' '); } @@ -3775,13 +3785,19 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, ref O if (t.next) { if (t.isref) - buf.write("(ref "); + { + openParenthesis(); + buf.write("ref "); + } typeToBuffer(t.next, null, buf, hgs); if (ident) buf.writeByte(' '); } else if (hgs.ddoc) + { + openParenthesis(); buf.writestring("auto "); + } if (ident) buf.writestring(ident); parametersToBuffer(t.parameterList, buf, hgs); @@ -3795,12 +3811,12 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, ref O void writeAttribute(string str) { - if (str == "ref") return; // 'ref' is handeld above + if (str == "ref") return; // 'ref' is handled above buf.writeByte(' '); buf.writestring(str); } t.attributesApply(&writeAttribute); - if (t.isref) + if (parenWritten) buf.writeByte(')'); t.inuse--; } From ef7a14e73ae8724b3946ed8779a49151361e11bc Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Wed, 20 Dec 2023 16:18:43 +0100 Subject: [PATCH 11/27] Parse linkage as part of function pointer and delegate types --- compiler/src/dmd/parse.d | 82 ++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 0777c202b720..527f51df75a5 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3502,6 +3502,20 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ AST.Type parseType(Identifier* pident = null, AST.TemplateParameters** ptpl = null, Loc* pdeclLoc = null) { + // Handle linkage for function pointer and delegate types + const bool hasLinkage = token.value == TOK.extern_; + LINK link = LINK.default_; + if (hasLinkage) + { + auto l = parseLinkage(); + // Reject C++-class-specific stuff + if (l.cppmangle != CPPMANGLE.def) + error("C++ mangle declaration not allowed here"); + if (l.idents != null || l.identExps != null) + error("C++ namespaces not allowed here"); + link = l.link; + printf("Linkage seen: %d\n", link); + } // Handle `ref` TypeCtors(opt) BasicType CallableSuffix TypeSuffixes(opt) const bool isRef = token.value == TOK.ref_; if (isRef) nextToken(); @@ -3560,7 +3574,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Type t; t = parseBasicType(); - if (isRef) + if (hasLinkage || isRef) { t = t.addSTC(stc); stc = STC.undefined_; @@ -3569,7 +3583,13 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (pdeclLoc) *pdeclLoc = token.loc; int alt = 0; - t = parseDeclarator(t, alt, pident, ptpl, 0, null, null, isRef); + t = parseDeclarator(t, alt, pident, ptpl, 0, null, null, isRef, link); + if (t.ty == Tdelegate || t.ty == Tpointer && (cast(AST.TypeNext)t).nextOf.ty == Tfunction) + { + auto ll = (cast(AST.TypeFunction)(cast(AST.TypeNext)t).nextOf).linkage; + if (ll != LINK.d) + printf("Linkage applied: %d\n", ll); + } checkCstyleTypeSyntax(typeLoc, t, alt, pident ? *pident : null); t = t.addSTC(stc); @@ -3946,13 +3966,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * See_Also: * https://dlang.org/spec/declaration.html#TypeSuffixes */ - private AST.Type parseTypeSuffixes(AST.Type t, bool isRefCallable = false) + private AST.Type parseTypeSuffixes(AST.Type t, bool isRef = false, LINK link = LINK.default_) { - // ref TypeCtors(opt) BasicType CallableSuffix TypeSuffixes(opt) - if (isRefCallable && token.value != TOK.delegate_ && token.value != TOK.function_) - error("`ref` is only valid for `function` and `delegate` types"); - //printf("parseTypeSuffixes()\n"); + const requireCallable = isRef || link != LINK.default_; + bool lastSuffixWasCallable = false; while (1) { switch (token.value) @@ -3960,6 +3978,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.mul: t = new AST.TypePointer(t); nextToken(); + lastSuffixWasCallable = false; continue; case TOK.leftBracket: @@ -4004,29 +4023,30 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer inBrackets--; check(TOK.rightBracket); } + lastSuffixWasCallable = false; continue; case TOK.delegate_: case TOK.function_: { - // Handle delegate declaration: - // ref(opt) type delegate(parameter list) nothrow pure - // ref(opt) type function(parameter list) nothrow pure + // Handle latter part of delegate declaration: + // Linkage(opt) ref(opt) type delegate(parameter list) nothrow pure + // Linkage(opt) ref(opt) type function(parameter list) nothrow pure const save = token.value; nextToken(); auto parameterList = parseParameterList(null); - StorageClass stc; - if (isRefCallable) - { - stc = parsePostfix(STC.ref_, null); - isRefCallable = false; // ref applies to the innermost - } - else - { - stc = parsePostfix(STC.undefined_, null); - } + StorageClass stc = parsePostfix(STC.undefined_, null); + // if (isRefCallable) + // { + // stc = parsePostfix(STC.ref_, null); + // isRefCallable = false; + // } + // else + // { + // stc = parsePostfix(STC.undefined_, null); + // } auto tf = new AST.TypeFunction(parameterList, t, linkage, stc); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild | STC.return_)) { @@ -4036,10 +4056,23 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer tf = cast(AST.TypeFunction)tf.addSTC(stc); } t = save == TOK.delegate_ ? new AST.TypeDelegate(tf) : new AST.TypePointer(tf); // pointer to function + lastSuffixWasCallable = true; continue; } default: - return t; + { + if (requireCallable && !lastSuffixWasCallable) + { + error("linkage and `ref` are only valid for `function` and `delegate` types"); + } + if (requireCallable) + { + auto fp = cast(AST.TypeFunction) (cast(AST.TypeNext)t).nextOf; + if (isRef) fp.isref = true; + if (link != LINK.default_) fp.linkage = link; + } + return t; + } } assert(0); } @@ -4063,10 +4096,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ private AST.Type parseDeclarator(AST.Type t, ref int palt, Identifier* pident, AST.TemplateParameters** tpl = null, StorageClass storageClass = 0, - bool* pdisable = null, AST.Expressions** pudas = null, bool isRefCallable = false) + bool* pdisable = null, AST.Expressions** pudas = null, bool isRefCallable = false, LINK link = LINK.default_) { //printf("parseDeclarator(tpl = %p)\n", tpl); - t = parseTypeSuffixes(t, isRefCallable); + t = parseTypeSuffixes(t, isRefCallable, link); AST.Type ts; switch (token.value) { @@ -4358,7 +4391,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer link = res.link; if (res.idents || res.identExps) { - error("C++ name spaces not allowed here"); + error("C++ namespaces not allowed here"); } if (res.cppmangle != CPPMANGLE.def) { @@ -5036,6 +5069,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer auto a2 = new AST.Dsymbols(); a2.push(s); s = new AST.LinkDeclaration(linkloc, link, a2); + printf("Uses LinkDeclaration: linkage = %d\n", link); } a.push(s); From eadcef3465bd2c1ac8e3ec2a38575477238d517f Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Mon, 17 Jun 2024 19:36:01 +0200 Subject: [PATCH 12/27] Require exactly one callable suffix after linakge or --- compiler/src/dmd/parse.d | 148 +++++++++++++++------------------------ 1 file changed, 58 insertions(+), 90 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index c37ea4823a87..9d081a547e7e 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3514,94 +3514,40 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ AST.Type parseType(Identifier* pident = null, AST.TemplateParameters** ptpl = null, Loc* pdeclLoc = null) { + immutable stc = parseTypeCtor(); // Handle linkage for function pointer and delegate types - const bool hasLinkage = token.value == TOK.extern_; - LINK link = LINK.default_; - if (hasLinkage) - { + immutable LINK link = () { + if (token.value != TOK.extern_) return LINK.default_; + auto l = parseLinkage(); // Reject C++-class-specific stuff if (l.cppmangle != CPPMANGLE.def) error("C++ mangle declaration not allowed here"); if (l.idents != null || l.identExps != null) error("C++ namespaces not allowed here"); - link = l.link; - printf("Linkage seen: %d\n", link); - } - // Handle `ref` TypeCtors(opt) BasicType CallableSuffix TypeSuffixes(opt) - const bool isRef = token.value == TOK.ref_; + return l.link; + // printf("Linkage seen: %d\n", link); + }(); + // Handle `ref` TypeCtors(opt) BasicType TypeSuffixes(opt) CallableSuffix NonCallableSuffixes(opt) + immutable bool isRef = token.value == TOK.ref_; if (isRef) nextToken(); - - /* Take care of the storage class prefixes that - * serve as type attributes: - * const type - * immutable type - * shared type - * inout type - * inout const type - * shared const type - * shared inout type - * shared inout const type - */ - StorageClass stc = 0; - while (1) - { - switch (token.value) - { - case TOK.const_: - if (peekNext() == TOK.leftParenthesis) - break; // const as type constructor - stc |= STC.const_; // const as storage class - nextToken(); - continue; - - case TOK.immutable_: - if (peekNext() == TOK.leftParenthesis) - break; - stc |= STC.immutable_; - nextToken(); - continue; - - case TOK.shared_: - if (peekNext() == TOK.leftParenthesis) - break; - stc |= STC.shared_; - nextToken(); - continue; - - case TOK.inout_: - if (peekNext() == TOK.leftParenthesis) - break; - stc |= STC.wild; - nextToken(); - continue; - - default: - break; - } - break; - } + immutable stc2 = parseTypeCtor(); const typeLoc = token.loc; AST.Type t; t = parseBasicType(); - if (hasLinkage || isRef) - { - t = t.addSTC(stc); - stc = STC.undefined_; - } if (pdeclLoc) *pdeclLoc = token.loc; int alt = 0; - t = parseDeclarator(t, alt, pident, ptpl, 0, null, null, isRef, link); - if (t.ty == Tdelegate || t.ty == Tpointer && (cast(AST.TypeNext)t).nextOf.ty == Tfunction) - { - auto ll = (cast(AST.TypeFunction)(cast(AST.TypeNext)t).nextOf).linkage; - if (ll != LINK.d) - printf("Linkage applied: %d\n", ll); - } + t = parseDeclarator(t, alt, pident, ptpl, stc2, null, null, isRef, link); + // if (t.ty == Tdelegate || t.ty == Tpointer && (cast(AST.TypeNext)t).nextOf.ty == Tfunction) + // { + // auto ll = (cast(AST.TypeFunction)(cast(AST.TypeNext)t).nextOf).linkage; + // if (ll != LINK.d) + // printf("Linkage applied: %d\n", ll); + // } checkCstyleTypeSyntax(typeLoc, t, alt, pident ? *pident : null); t = t.addSTC(stc); @@ -3978,11 +3924,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * See_Also: * https://dlang.org/spec/declaration.html#TypeSuffixes */ - private AST.Type parseTypeSuffixes(AST.Type t, bool isRef = false, LINK link = LINK.default_) + private AST.Type parseTypeSuffixes(AST.Type t, StorageClass stc2 = 0, bool isRef = false, LINK link = LINK.default_) { //printf("parseTypeSuffixes()\n"); const requireCallable = isRef || link != LINK.default_; - bool lastSuffixWasCallable = false; + AST.TypeFunction tf = null; while (1) { switch (token.value) @@ -3990,7 +3936,6 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.mul: t = new AST.TypePointer(t); nextToken(); - lastSuffixWasCallable = false; continue; case TOK.leftBracket: @@ -4035,7 +3980,6 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer inBrackets--; check(TOK.rightBracket); } - lastSuffixWasCallable = false; continue; case TOK.delegate_: @@ -4044,7 +3988,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer // Handle latter part of delegate declaration: // Linkage(opt) ref(opt) type delegate(parameter list) nothrow pure // Linkage(opt) ref(opt) type function(parameter list) nothrow pure - const save = token.value; + immutable callableKeyword = token.value; nextToken(); auto parameterList = parseParameterList(null); @@ -4059,29 +4003,38 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer // { // stc = parsePostfix(STC.undefined_, null); // } - auto tf = new AST.TypeFunction(parameterList, t, linkage, stc); + if (tf !is null) + { + if (link != LINK.default_ && isRef) error("Linkage and `ref` are ambiguous. Use clarifying parentheses."); + else if (isRef) error("`ref` is ambiguous. Use clarifying parentheses."); + else if (link != LINK.default_) error("Linkage is ambiguous. Use clarifying parentheses."); + } + tf = new AST.TypeFunction(parameterList, t, linkage, stc); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild | STC.return_)) { - if (save == TOK.function_) + if (callableKeyword == TOK.function_) error("`const`/`immutable`/`shared`/`inout`/`return` attributes are only valid for non-static member functions"); else tf = cast(AST.TypeFunction)tf.addSTC(stc); } - t = save == TOK.delegate_ ? new AST.TypeDelegate(tf) : new AST.TypePointer(tf); // pointer to function - lastSuffixWasCallable = true; + t = callableKeyword == TOK.delegate_ + ? new AST.TypeDelegate(tf) + : new AST.TypePointer(tf); // pointer to function continue; } default: { - if (requireCallable && !lastSuffixWasCallable) - { - error("linkage and `ref` are only valid for `function` and `delegate` types"); - } if (requireCallable) { - auto fp = cast(AST.TypeFunction) (cast(AST.TypeNext)t).nextOf; - if (isRef) fp.isref = true; - if (link != LINK.default_) fp.linkage = link; + if (tf is null) + { + if (link != LINK.default_ && isRef) error("linkage and `ref` are only valid for `function` and `delegate` types"); + else if (isRef) error("`ref` is only valid for `function` and `delegate` types"); + else error("linkage is only valid for `function` and `delegate` types"); + } + tf.next = tf.next.addSTC(stc2); + if (isRef) tf.isref = true; + if (link != LINK.default_) tf.linkage = link; } return t; } @@ -4111,7 +4064,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer bool* pdisable = null, AST.Expressions** pudas = null, bool isRefCallable = false, LINK link = LINK.default_) { //printf("parseDeclarator(tpl = %p)\n", tpl); - t = parseTypeSuffixes(t, isRefCallable, link); + t = parseTypeSuffixes(t, storageClass, isRefCallable, link); AST.Type ts; switch (token.value) { @@ -5081,7 +5034,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer auto a2 = new AST.Dsymbols(); a2.push(s); s = new AST.LinkDeclaration(linkloc, link, a2); - printf("Uses LinkDeclaration: linkage = %d\n", link); + // printf("Uses LinkDeclaration: linkage = %d\n", link); } a.push(s); @@ -9605,8 +9558,23 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } const stc = parseTypeCtor(); + const LINK link = () { + if (token.value != TOK.extern_) return LINK.default_; + + auto l = parseLinkage(); + // Reject C++-class-specific stuff + if (l.cppmangle != CPPMANGLE.def) + error("C++ mangle declaration not allowed here"); + if (l.idents != null || l.identExps != null) + error("C++ namespaces not allowed here"); + return l.link; + }(); + // Handle `ref` TypeCtors(opt) BasicType TypeSuffixes(opt) CallableSuffix NonCallableSuffixes(opt) + const bool isRef = token.value == TOK.ref_; + if (isRef) nextToken(); + const stc2 = parseTypeCtor(); auto t = parseBasicType(true); - t = parseTypeSuffixes(t); + t = parseTypeSuffixes(t, stc2, isRef, link); t = t.addSTC(stc); if (t.ty == Taarray) { From b73df138485103c3bf5809a078551066d54c1980 Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Mon, 24 Jun 2024 17:03:50 +0200 Subject: [PATCH 13/27] Add support for parsing linkage on function literals --- compiler/src/dmd/parse.d | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index cd779f83ed9e..bcc888937f61 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -5080,6 +5080,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.TemplateParameters* tpl = null; AST.ParameterList parameterList; AST.Type tret = null; + LINK linkage = LINK.default_; StorageClass stc = 0; TOK save = TOK.reserved; @@ -5089,6 +5090,16 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.delegate_: save = token.value; nextToken(); + if (token.value == TOK.extern_) + { + ParsedLinkage!(AST) pl = parseLinkage(); + // Reject C++-class-specific stuff + if (pl.cppmangle != CPPMANGLE.def) + error("C++ mangle declaration not allowed here"); + if (pl.idents != null || pl.identExps != null) + error("C++ namespaces not allowed here"); + linkage = pl.link; + } if (token.value == TOK.auto_) { nextToken(); @@ -5197,6 +5208,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer auto tf = new AST.TypeFunction(parameterList, tret, linkage, stc); tf = cast(AST.TypeFunction)tf.addSTC(stc); + tf.linkage = linkage; auto fd = new AST.FuncLiteralDeclaration(loc, Loc.initial, tf, save, null, null, stc & STC.auto_); if (token.value == TOK.goesTo) From f40e9b138cf681f13eaf4df0bfd90c411f43d7aa Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Mon, 24 Jun 2024 17:05:43 +0200 Subject: [PATCH 14/27] Handle and diagnose ambiguous `ref` target for nested function ptr/delegate types --- compiler/src/dmd/hdrgen.d | 17 +++++++++ compiler/src/dmd/mtype.d | 28 ++++++++++++++- compiler/src/dmd/parse.d | 74 +++++++++++++++++++++++++++------------ 3 files changed, 96 insertions(+), 23 deletions(-) diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index ad21e1bd7bbf..4946bd361327 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -3903,7 +3903,24 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, ref O openParenthesis(); buf.write("ref "); } + immutable bool hasNestedNonParendCallable = { + for ({ Type tt = t; TypeNext tn = null; } (tn = tt.isTypeNext) !is null;) + { + tt = tn.next; + switch (tt.ty) + { + case Tsarray, Tarray, Taarray, Tpointer, Treference, Tdelegate, Tslice: continue; + case Tfunction: + TypeFunction tf = cast(TypeFunction) tt; + return !tf.isref && tf.linkage <= LINK.d; + default: return false; + } + } + return false; + }(); + if (hasNestedNonParendCallable) buf.writeByte('('); typeToBuffer(t.next, null, buf, hgs); + if (hasNestedNonParendCallable) buf.writeByte(')'); if (ident) buf.writeByte(' '); } diff --git a/compiler/src/dmd/mtype.d b/compiler/src/dmd/mtype.d index cf9b1d71db8c..9d1a518e1333 100644 --- a/compiler/src/dmd/mtype.d +++ b/compiler/src/dmd/mtype.d @@ -1607,6 +1607,28 @@ extern (C++) abstract class Type : ASTNode inout(TypeTraits) isTypeTraits() { return ty == Ttraits ? cast(typeof(return))this : null; } inout(TypeNoreturn) isTypeNoreturn() { return ty == Tnoreturn ? cast(typeof(return))this : null; } inout(TypeTag) isTypeTag() { return ty == Ttag ? cast(typeof(return))this : null; } + + inout(TypeArray) isTypeArray() + { + switch (ty) + { + case Tsarray, Tarray, Taarray: + return cast(typeof(return))this; + default: + return null; + } + } + inout(TypeNext) isTypeNext() + { + switch (ty) + { + case Tsarray, Tarray, Taarray: + case Tpointer, Treference, Tfunction, Tdelegate, Tslice: + return cast(typeof(return))this; + default: + return null; + } + } } override void accept(Visitor v) @@ -1684,6 +1706,8 @@ extern (C++) abstract class TypeNext : Type this.next = next; } + abstract override TypeNext syntaxCopy(); + override final int hasWild() const { if (ty == Tfunction) @@ -1695,7 +1719,7 @@ extern (C++) abstract class TypeNext : Type /******************************* * For TypeFunction, nextOf() can return NULL if the function return - * type is meant to be inferred, and semantic() hasn't yet ben run + * type is meant to be inferred, and semantic() hasn't yet been run * on the function. After semantic(), it must no longer be NULL. */ override final Type nextOf() @safe @@ -2495,6 +2519,8 @@ extern (C++) abstract class TypeArray : TypeNext super(ty, next); } + abstract override TypeArray syntaxCopy(); + override void accept(Visitor v) { v.visit(this); diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index bcc888937f61..6661bb7ec11c 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3924,10 +3924,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * See_Also: * https://dlang.org/spec/declaration.html#TypeSuffixes */ - private AST.Type parseTypeSuffixes(AST.Type t, StorageClass stc2 = 0, bool isRef = false, LINK link = LINK.default_) + private AST.Type parseTypeSuffixes(AST.Type t, immutable StorageClass stc2 = 0, immutable bool isRef = false, immutable LINK link = LINK.default_) { //printf("parseTypeSuffixes()\n"); - const requireCallable = isRef || link != LINK.default_; + immutable linkageSpecified = link != LINK.default_; + immutable requireCallable = isRef || linkageSpecified; AST.TypeFunction tf = null; while (1) { @@ -3993,22 +3994,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer auto parameterList = parseParameterList(null); - StorageClass stc = parsePostfix(STC.undefined_, null); - // if (isRefCallable) - // { - // stc = parsePostfix(STC.ref_, null); - // isRefCallable = false; - // } - // else - // { - // stc = parsePostfix(STC.undefined_, null); - // } - if (tf !is null) - { - if (link != LINK.default_ && isRef) error("Linkage and `ref` are ambiguous. Use clarifying parentheses."); - else if (isRef) error("`ref` is ambiguous. Use clarifying parentheses."); - else if (link != LINK.default_) error("Linkage is ambiguous. Use clarifying parentheses."); - } + immutable StorageClass stc = parsePostfix(STC.undefined_, null); + immutable bool ambiguous = requireCallable && tf !is null; tf = new AST.TypeFunction(parameterList, t, linkage, stc); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild | STC.return_)) { @@ -4020,6 +4007,45 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer t = callableKeyword == TOK.delegate_ ? new AST.TypeDelegate(tf) : new AST.TypePointer(tf); // pointer to function + if (ambiguous) + { + // seek next inner function + auto tt = (cast(AST.TypeNext)t).syntaxCopy; + AST.TypeFunction oldTf = null; + for ({AST.Type tx = (cast(AST.TypeFunction)tt.next).next; AST.TypeNext tn; } (tn = tx.isTypeNext) !is null; tx = tn.next) + { + if (tn.ty == Tfunction) + { + auto tfn = cast(AST.TypeFunction)tn; + if (isRef) tfn.isref = true; + if (linkageSpecified) tfn.linkage = link; + goto Lfound; + } + } + assert(0); + Lfound: + if (isRef && linkageSpecified) + { + error("Linkage and `ref` could refer to more than one `function` or `delegate` here."); + eSink.errorSupplemental(token.loc, "Suggested clarifying parentheses:"); + eSink.errorSupplemental(token.loc, " `extern (%s) ref %s` (possibly in parentheses)", AST.linkageToChars(link), t.toChars()); + eSink.errorSupplemental(token.loc, "or `%s`", tt.toChars()); + } + else if (isRef) + { + error("`ref` could refer to more than one `function` or `delegate` here."); + eSink.errorSupplemental(token.loc, "Suggested clarifying parentheses:"); + eSink.errorSupplemental(token.loc, " `ref %s` (possibly in parentheses)", t.toChars()); + eSink.errorSupplemental(token.loc, "or `%s`", tt.toChars()); + } + else if (linkageSpecified) + { + error("Linkage could refer to more than one `function` or `delegate` here."); + eSink.errorSupplemental(token.loc, "Suggested clarifying parentheses:"); + eSink.errorSupplemental(token.loc, " `extern (%s) %s` (possibly in parentheses)", AST.linkageToChars(link), t.toChars()); + eSink.errorSupplemental(token.loc, "or `%s`", tt.toChars()); + } + } continue; } default: @@ -4028,13 +4054,17 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { if (tf is null) { - if (link != LINK.default_ && isRef) error("linkage and `ref` are only valid for `function` and `delegate` types"); - else if (isRef) error("`ref` is only valid for `function` and `delegate` types"); - else error("linkage is only valid for `function` and `delegate` types"); + if (linkageSpecified && isRef) error("Linkage and `ref` are only valid for `function` and `delegate` types"); + else if (isRef) + { + error("`ref` is not a type qualifier."); + eSink.errorSupplemental(token.loc, "It is only valid in function parameter lists and to indicate a function or delegate returns by reference."); + } + else error("Linkage is only valid for `function` and `delegate` types"); } tf.next = tf.next.addSTC(stc2); if (isRef) tf.isref = true; - if (link != LINK.default_) tf.linkage = link; + if (linkageSpecified) tf.linkage = link; } return t; } From 81b311e69735ada85e5b79df065fdd92bd4c827b Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Thu, 25 Jul 2024 21:18:09 +0200 Subject: [PATCH 15/27] Address `ref` variables being merged --- compiler/src/dmd/mtype.d | 2 +- compiler/src/dmd/parse.d | 200 +++++++++++++++++++++------ compiler/test/runnable/reffunctype.d | 4 +- 3 files changed, 163 insertions(+), 43 deletions(-) diff --git a/compiler/src/dmd/mtype.d b/compiler/src/dmd/mtype.d index 23943048653e..ae0369cd2116 100644 --- a/compiler/src/dmd/mtype.d +++ b/compiler/src/dmd/mtype.d @@ -1572,7 +1572,7 @@ extern (C++) abstract class Type : ASTNode default: return null; } - } + } } override void accept(Visitor v) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 6661bb7ec11c..384eb418fa3a 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3514,6 +3514,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ AST.Type parseType(Identifier* pident = null, AST.TemplateParameters** ptpl = null, Loc* pdeclLoc = null) { + //printf("parseType()\n"); immutable stc = parseTypeCtor(); // Handle linkage for function pointer and delegate types immutable LINK link = () { @@ -4058,7 +4059,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer else if (isRef) { error("`ref` is not a type qualifier."); - eSink.errorSupplemental(token.loc, "It is only valid in function parameter lists and to indicate a function or delegate returns by reference."); + eSink.errorSupplemental(token.loc, "To form a basic type, it must be followed by a function pointer or delegate suffix."); } else error("Linkage is only valid for `function` and `delegate` types"); } @@ -5801,6 +5802,47 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return param; } + /++ + + Returns whether `t` probably points to the start of a lambda expression. + +/ + private bool isLikelyLambdaExpressionStart(Token* t) + { + // With `function` or `delegate`, we assume the case is clear. + if (t.value == TOK.function_ || t.value == TOK.delegate_) + return true; + + // Same with `{}` or `identifier =>` + if (t.value == TOK.leftCurly || t.value == TOK.identifier && peek(t).value == TOK.goesTo) + return true; + + // The hard part: `auto`/`auto ref` Parameters MemberFunctionAttributes? FunctionLiteralBody + if (t.value == TOK.auto_) t = peek(t); + if (t.value == TOK.ref_) t = peek(t); + + if (t.value != TOK.leftParenthesis) + return false; + // Parameter list: Just assume it’s fine. + size_t nesting = 1; + do + { + t = peek(t); + nesting += (t.value == TOK.leftParenthesis) - (t.value == TOK.rightParenthesis); + } + while (nesting > 0 || t.value != TOK.rightParenthesis); + // MemberFunctionAttributes: + do + t = peek(t); + while ( + // Proper member function attributes: + t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || + t.value == TOK.return_ || t.value == TOK.scope_ || t.value == TOK.shared_ || + // Function attributes: + t.value == TOK.nothrow_ || t.value == TOK.pure_ || + (t.value == TOK.at && (t = peek(t)).value == TOK.identifier) + ); + return t.value == TOK.goesTo || t.value == TOK.leftCurly; + } + /***************************************** * Input: * flags PSxxxx @@ -6025,15 +6067,14 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer goto Lexp; if (peekNext() == TOK.leftParenthesis) goto Lexp; - goto case; + goto Ldeclaration; // FunctionLiteral `auto ref (` + // FunctionLiteral: `ref (` + // Reference variable: `ref BasicType` case TOK.auto_: - if (peekNext() == TOK.ref_ && peekNext2() == TOK.leftParenthesis) - goto Lexp; - goto Ldeclaration; case TOK.ref_: - if (peekNext() == TOK.leftParenthesis) + if (isLikelyLambdaExpressionStart(&token)) goto Lexp; goto Ldeclaration; @@ -7224,6 +7265,23 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer mustIfDstyle, // Declarator part must have identifier, but don't recognize old C-style syntax } + /++ + + Returns whether the token starts `scope(exit)`, `scope(failure)`, or `scope(success)`. + +/ + private bool isScopeGuard(Token* t) + { + if (t.value != TOK.scope_) return false; + t = peek(t); + if (t.value != TOK.leftParenthesis) return false; + t = peek(t); + if (t.value != TOK.identifier) return false; + if (t.ident != Id.exit && t.ident != Id.failure && t.ident != Id.success) + return false; + t = peek(t); + if (t.value != TOK.rightParenthesis) return false; + return true; + } + /************************************ * Determine if the scanner is sitting on the start of a declaration. * Params: @@ -7236,45 +7294,57 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ private bool isDeclaration(Token* t, NeedDeclaratorId needId, TOK endtok, Token** pt) { - //printf("isDeclaration(needId = %d)\n", needId); - int haveId = 0; - int haveTpl = 0; + // printf("isDeclaration(needId: %d) %s\n", needId, t.toChars()); - while (1) + // `scope` can be a storage class, but a scope guard takes priority + if (isScopeGuard(t)) return false; + + bool skipStroageClassTypeCtor() { - if ((t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) && peek(t).value != TOK.leftParenthesis) + bool isTypeCtor() => t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_; + bool isFreestandingTypeCtor() => isTypeCtor() && peek(t).value != TOK.leftParenthesis; + bool storageClassSeen = false; + while (isFreestandingTypeCtor() || t.value == TOK.ref_ || t.value == TOK.auto_ || t.value == TOK.scope_) { - /* const type - * immutable type - * shared type - * wild type - */ t = peek(t); - continue; + storageClassSeen = true; } - break; + return storageClassSeen; } - if (!isBasicType(&t)) + immutable bool anyStorageClass = skipStroageClassTypeCtor(); + // If there are any storage classes, a type is optional and inferred if not present. + // That is exactly the case when an identifier follows plus either an opening parentheses (function declaration) or `=`. + if (!anyStorageClass || t.value != TOK.identifier || peek(t).value != TOK.leftParenthesis && peek(t).value != TOK.assign) { - goto Lisnot; + const bool isBT = isBasicType(&t); + // printf("isDeclaration() (%d,%d) isBasicType: %d; anyStorageClass: %d\n", t.loc.linnum, t.loc.charnum, isBT, anyStorageClass); + if (!isBT && !anyStorageClass) + { + goto Lisnot; + } } - if (!isDeclarator(&t, &haveId, &haveTpl, endtok, needId != NeedDeclaratorId.mustIfDstyle)) - goto Lisnot; - // needed for `__traits(compiles, arr[0] = 0)` - if (!haveId && t.value == TOK.assign) - goto Lisnot; - if ((needId == NeedDeclaratorId.no && !haveId) || - (needId == NeedDeclaratorId.opt) || - (needId == NeedDeclaratorId.must && haveId) || - (needId == NeedDeclaratorId.mustIfDstyle && haveId)) { - if (pt) - *pt = t; - goto Lis; + int haveId = 0; + int haveTpl = 0; + const bool isDecl = isDeclarator(&t, &haveId, &haveTpl, endtok, allowAltSyntax: anyStorageClass || needId != NeedDeclaratorId.mustIfDstyle); + // printf("isDeclaration() (%d,%d) isDeclarator: %d\n", t.loc.linnum, t.loc.charnum, isDecl); + if (!isDecl) + goto Lisnot; + // needed for `__traits(compiles, arr[0] = 0)` + if (!haveId && t.value == TOK.assign) + goto Lisnot; + if ((needId == NeedDeclaratorId.no && !haveId) || + (needId == NeedDeclaratorId.opt) || + (needId == NeedDeclaratorId.must && haveId) || + (needId == NeedDeclaratorId.mustIfDstyle && haveId)) + { + if (pt) + *pt = t; + goto Lis; + } + goto Lisnot; } - goto Lisnot; - Lis: //printf("\tis declaration, t = %s\n", t.toChars()); return true; @@ -7462,17 +7532,62 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.leftParenthesis: // (type) t = peek(t); - const bool isRef = t.value == TOK.ref_; + while (t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) + t = peek(t); + bool hasLinkage = false; + if (t.value == TOK.extern_) + { + t = peek(t); + if (t.value != TOK.leftParenthesis) + goto Lfalse; + t = peek(t); + if (t.value != TOK.identifier) goto Lfalse; + switch (t.ident.toString()) + { + case "D": + case "System": + case "Windows": + t = peek(t); + if (t.value != TOK.rightParenthesis) goto Lfalse; + t = peek(t); + break; + + case "C": // C or C++ + t = peek(t); + if (t.value == TOK.plusPlus) // C++ linkage + { + t = peek(t); + } + if (t.value != TOK.rightParenthesis) goto Lfalse; + t = peek(t); + break; + + case "Objective": // Objective-C + t = peek(t); + if (t.value != TOK.min) goto Lfalse; + t = peek(t); + if (t.value != TOK.identifier || t.ident.toString() != "C") goto Lfalse; + t = peek(t); + if (t.value != TOK.rightParenthesis) goto Lfalse; + t = peek(t); + break; + + default: + goto Lfalse; + } + + hasLinkage = true; + } + immutable bool isRef = t.value == TOK.ref_; if (isRef) t = peek(t); while (t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) t = peek(t); if (!isBasicType(&t)) goto Lfalse; - if (isRef && t.value != TOK.function_ && t.value != TOK.delegate_) - goto Lfalse; + int haveId = 1; int haveTpl = 0; - if (!isDeclarator(&t, &haveId, &haveTpl, TOK.rightParenthesis)) + if (!isDeclarator(&t, &haveId, &haveTpl, TOK.rightParenthesis, requireCallable: hasLinkage || isRef)) goto Lfalse; if (t.value != TOK.rightParenthesis) goto Lfalse; @@ -7496,11 +7611,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return false; } - private bool isDeclarator(Token** pt, int* haveId, int* haveTpl, TOK endtok, bool allowAltSyntax = true) + private bool isDeclarator(Token** pt, int* haveId, int* haveTpl, TOK endtok, bool allowAltSyntax = true, bool requireCallable = false) { // This code parallels parseDeclarator() Token* t = *pt; bool parens; + uint callables = 0; //printf("Parser::isDeclarator() %s\n", t.toChars()); if (t.value == TOK.assign) @@ -7520,6 +7636,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer t = peek(t); if (t.value == TOK.rightBracket) { + // [ ] t = peek(t); } else if (isDeclaration(t, NeedDeclaratorId.no, TOK.rightBracket, &t)) @@ -7604,6 +7721,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (!isParameters(&t)) return false; skipAttributes(t, &t); + ++callables; continue; default: @@ -7612,6 +7730,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer break; } + if (requireCallable && callables != 1) return false; + while (1) { switch (t.value) @@ -9697,7 +9817,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { if (mod.edition >= Edition.v2024) { - eSink.error(token.loc, "usage of identifer `body` as a keyword is obsolete. Use `do` instead."); + eSink.error(token.loc, "usage of identifier `body` as a keyword is obsolete. Use `do` instead."); } } } diff --git a/compiler/test/runnable/reffunctype.d b/compiler/test/runnable/reffunctype.d index 7375ec61e648..ef87cae76c0b 100644 --- a/compiler/test/runnable/reffunctype.d +++ b/compiler/test/runnable/reffunctype.d @@ -6,7 +6,7 @@ void a( ref (int function()) ) { } void b((ref int function()) ) { } -// `c` is `a` without clarifiying parentheses. +// `c` is `a` without clarifying parentheses. void c( ref int function() ) { } static assert(!is( typeof(&a) == typeof(&b) )); @@ -18,7 +18,7 @@ static assert( is( typeof(&a) == typeof(&c) )); ref (int function()) x() { static typeof(return) fp = null; return fp; } (ref int function()) y() => null; -// `z` is `x` without clarifiying parentheses. +// `z` is `x` without clarifying parentheses. ref int function() z() => x(); static assert(!is( typeof(&x) == typeof(&y) )); From 5df15c3b765f796dc5bf1e66548f4ab7d9708f2a Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Fri, 20 Sep 2024 01:42:01 +0200 Subject: [PATCH 16/27] Semantically apply linkage to types --- compiler/src/dmd/parse.d | 58 +++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 2cdb6472545f..d0f89d0ffd08 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3936,7 +3936,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer //printf("parseTypeSuffixes()\n"); immutable linkageSpecified = link != LINK.default_; immutable requireCallable = isRef || linkageSpecified; - AST.TypeFunction tf = null; + AST.TypeFunction tf = null; // The function type underlying the last function pointer or delegate suffix + AST.TypeNext tn = null; // last function pointer or delegate suffix while (1) { switch (token.value) @@ -4011,19 +4012,22 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer else tf = cast(AST.TypeFunction)tf.addSTC(stc); } - t = callableKeyword == TOK.delegate_ + t = tn = callableKeyword == TOK.delegate_ ? new AST.TypeDelegate(tf) : new AST.TypePointer(tf); // pointer to function if (ambiguous) { // seek next inner function auto tt = (cast(AST.TypeNext)t).syntaxCopy; - AST.TypeFunction oldTf = null; - for ({AST.Type tx = (cast(AST.TypeFunction)tt.next).next; AST.TypeNext tn; } (tn = tx.isTypeNext) !is null; tx = tn.next) + for ( + {AST.Type tnextt = (cast(AST.TypeFunction)tt.next).next; AST.TypeNext tnextn; } + (tnextn = tnextt.isTypeNext) !is null; + tnextt = tnextn.next + ) { - if (tn.ty == Tfunction) + if (tnextn.ty == Tfunction) { - auto tfn = cast(AST.TypeFunction)tn; + auto tfn = cast(AST.TypeFunction)tnextn; if (isRef) tfn.isref = true; if (linkageSpecified) tfn.linkage = link; goto Lfound; @@ -4071,7 +4075,37 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } tf.next = tf.next.addSTC(stc2); if (isRef) tf.isref = true; - if (linkageSpecified) tf.linkage = link; + if (linkageSpecified) + { + // tf.linkage = link; + // NOTE: The above should work, but does not. + // Everything that follows is a workaround. + + // Idea: Replace tn by + // typeof(function{ extern() Date: Fri, 20 Sep 2024 01:54:22 +0200 Subject: [PATCH 17/27] Fix build error on AutoTest etc. --- compiler/src/dmd/parse.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index d0f89d0ffd08..2cfc5a00249b 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -7343,8 +7343,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer bool skipStroageClassTypeCtor() { - bool isTypeCtor() => t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_; - bool isFreestandingTypeCtor() => isTypeCtor() && peek(t).value != TOK.leftParenthesis; + bool isTypeCtor() { return t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_; } + bool isFreestandingTypeCtor() { return isTypeCtor() && peek(t).value != TOK.leftParenthesis; } bool storageClassSeen = false; while (isFreestandingTypeCtor() || t.value == TOK.ref_ || t.value == TOK.auto_ || t.value == TOK.scope_) { From fcfe35d3fac1f136653d7e1696d7e1c6f7059497 Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Sat, 21 Sep 2024 02:40:20 +0200 Subject: [PATCH 18/27] Remove trailing whitespace --- compiler/src/dmd/parse.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 2cfc5a00249b..76301d65f373 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -4094,9 +4094,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer new AST.ReturnStatement(loc_, new AST.IdentifierExp(loc_, resultId)) ); auto typeoftype = new AST.TypeTypeof(loc_, new AST.CallExp(loc_, new AST.FuncExp(loc_, fd))); - + if (t is tn) return typeoftype; - + AST.TypeNext tx = t.isTypeNext(); assert(tx !is null); while (tx.next !is tn) From 2ff41916788e0d4bb0347dd5c31ada1ad114b3f2 Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Sat, 21 Sep 2024 02:41:22 +0200 Subject: [PATCH 19/27] Implement linkage for lambdas --- compiler/src/dmd/parse.d | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 76301d65f373..4ec247fa76d5 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -8862,6 +8862,23 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { AST.Dsymbol s = parseFunctionLiteral(); e = new AST.FuncExp(loc, s); + + if (!s.isTemplateDeclaration) + { + auto tf = cast(AST.TypeFunction) (cast(AST.FuncLiteralDeclaration) s).type; + if (tf.linkage != LINK.default_) + { + auto resultId = new Identifier("__result"); + auto fd = new AST.FuncLiteralDeclaration(loc, Loc.initial, new AST.TypeFunction(AST.ParameterList(), null, LINK.default_), TOK.delegate_, null); + auto vardecl = new AST.Dsymbols(); + vardecl.push(new AST.VarDeclaration(loc, null, resultId, new AST.ExpInitializer(loc, e))); + fd.fbody = new AST.CompoundStatement(loc, + new AST.ExpStatement(loc, new AST.DeclarationExp(loc, new AST.LinkDeclaration(loc, tf.linkage, vardecl))), + new AST.ReturnStatement(loc, new AST.IdentifierExp(loc, resultId)) + ); + e = new AST.CallExp(loc, new AST.FuncExp(loc, fd)); + } + } break; } From c50fd397857a5d1964fc9f3bc5e2ae2b079bfa3f Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Sat, 21 Sep 2024 22:37:47 +0200 Subject: [PATCH 20/27] Implement linkage for alias to template lambda --- compiler/src/dmd/parse.d | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 4ec247fa76d5..faa8c8162e54 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -5034,6 +5034,14 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } v = new AST.AliasDeclaration(loc, ident, s); + + if (auto tpl_ = s.isTemplateDeclaration) + { + assert(tpl_.members.length == 1); + auto fd = cast(AST.FuncLiteralDeclaration) (*tpl_.members)[0]; + auto tf = cast(AST.TypeFunction) fd.type; + link = tf.linkage; + } } else { From 48077f986a3cf44f32e236bb70a18657d72110d8 Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Sat, 21 Sep 2024 22:39:10 +0200 Subject: [PATCH 21/27] Make linkage for non-alias template lambdas an error --- compiler/src/dmd/parse.d | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index faa8c8162e54..356b62bbdd58 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -8871,7 +8871,17 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Dsymbol s = parseFunctionLiteral(); e = new AST.FuncExp(loc, s); - if (!s.isTemplateDeclaration) + if (auto tpl = s.isTemplateDeclaration) + { + assert(tpl.members.length == 1); + auto fd = cast(AST.FuncLiteralDeclaration) (*tpl.members)[0]; + auto tf = cast(AST.TypeFunction) fd.type; + if (tf.linkage != LINK.default_) + { + error("Explicit linkage for template lambdas is not supported, except for alias declarations."); + } + } + else { auto tf = cast(AST.TypeFunction) (cast(AST.FuncLiteralDeclaration) s).type; if (tf.linkage != LINK.default_) From d93ed13c6780b55e60f220ad949c71cd2033154a Mon Sep 17 00:00:00 2001 From: Quirin Schroll Date: Tue, 24 Sep 2024 21:43:10 +0200 Subject: [PATCH 22/27] Fix some parsing issues with lambdas and `scope`, improve error messages --- compiler/src/dmd/lexer.d | 9 + compiler/src/dmd/parse.d | 368 ++++++++++++++++++++++++++------------- 2 files changed, 252 insertions(+), 125 deletions(-) diff --git a/compiler/src/dmd/lexer.d b/compiler/src/dmd/lexer.d index 2a11f3080179..eac5878836db 100644 --- a/compiler/src/dmd/lexer.d +++ b/compiler/src/dmd/lexer.d @@ -311,6 +311,15 @@ class Lexer return peek(t).value; } + /*********************** + * Look 3 tokens ahead at value. + */ + final TOK peekNext3() + { + Token* t = peek(peek(&token)); + return peek(t).value; + } + /**************************** * Turn next token in buffer into a token. * Params: diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 356b62bbdd58..366370df0d46 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3512,6 +3512,21 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return decldefs; } + /** + * Encapsulates linkage and ref-return information + * when parsing a function pointer or delegate type. + * The `link2` member is used for better error handling. + */ + private static struct CallableIntroducer + { + LINK link; + LINK link2; + bool isRef; + bool isRef2; + + bool linkageSpecified() const { return (link | link2) != LINK.default_; } + } + /* Parse a type and optional identifier * Params: * pident = set to Identifier if there is one, null if not @@ -3522,22 +3537,46 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { //printf("parseType()\n"); immutable stc = parseTypeCtor(); - // Handle linkage for function pointer and delegate types - immutable LINK link = () { - if (token.value != TOK.extern_) return LINK.default_; - auto l = parseLinkage(); - // Reject C++-class-specific stuff - if (l.cppmangle != CPPMANGLE.def) - error("C++ mangle declaration not allowed here"); - if (l.idents != null || l.identExps != null) - error("C++ namespaces not allowed here"); - return l.link; - // printf("Linkage seen: %d\n", link); + // Parse up to two sets of Linkage and `ref` for better error messages in parseTypeSuffixes + // when parentheses are omitted. + immutable CallableIntroducer intro = { + CallableIntroducer result; + + // Handle linkage for function pointer and delegate types + LINK getLinkage() + { + if (token.value != TOK.extern_) return LINK.default_; + + auto l = parseLinkage(); + // Reject C++-class-specific stuff + if (l.cppmangle != CPPMANGLE.def) + error("C++ mangle declaration not allowed here"); + if (l.idents != null || l.identExps != null) + error("C++ namespaces not allowed here"); + return l.link; + // printf("Linkage seen: %d\n", link); + } + result.link = getLinkage(); + + // Handle `ref` TypeCtors(opt) BasicType TypeSuffixes(opt) CallableSuffix NonCallableSuffixes(opt) + result.isRef = token.value == TOK.ref_; + if (result.isRef) + { + nextToken(); + } + + result.link2 = getLinkage(); + + result.isRef2 = token.value == TOK.ref_; + if (result.isRef2) + { + nextToken(); + } + + return result; }(); - // Handle `ref` TypeCtors(opt) BasicType TypeSuffixes(opt) CallableSuffix NonCallableSuffixes(opt) - immutable bool isRef = token.value == TOK.ref_; - if (isRef) nextToken(); + immutable stc2 = parseTypeCtor(); const typeLoc = token.loc; @@ -3548,13 +3587,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (pdeclLoc) *pdeclLoc = token.loc; int alt = 0; - t = parseDeclarator(t, alt, pident, ptpl, stc2, null, null, isRef, link); - // if (t.ty == Tdelegate || t.ty == Tpointer && (cast(AST.TypeNext)t).nextOf.ty == Tfunction) - // { - // auto ll = (cast(AST.TypeFunction)(cast(AST.TypeNext)t).nextOf).linkage; - // if (ll != LINK.d) - // printf("Linkage applied: %d\n", ll); - // } + t = parseDeclarator(t, alt, pident, ptpl, stc2, null, null, intro); checkCstyleTypeSyntax(typeLoc, t, alt, pident ? *pident : null); t = t.addSTC(stc); @@ -3931,11 +3964,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * See_Also: * https://dlang.org/spec/declaration.html#TypeSuffixes */ - private AST.Type parseTypeSuffixes(AST.Type t, immutable StorageClass stc2 = 0, immutable bool isRef = false, immutable LINK link = LINK.default_) + private AST.Type parseTypeSuffixes(AST.Type t, immutable StorageClass stc2 = 0, immutable CallableIntroducer intro = CallableIntroducer()) { //printf("parseTypeSuffixes()\n"); - immutable linkageSpecified = link != LINK.default_; - immutable requireCallable = isRef || linkageSpecified; + immutable requireCallable = intro.isRef || intro.linkageSpecified; + bool ambiguous = false; // will be true if `requireCallable` and there is more than one callable suffix AST.TypeFunction tf = null; // The function type underlying the last function pointer or delegate suffix AST.TypeNext tn = null; // last function pointer or delegate suffix while (1) @@ -4003,7 +4036,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer auto parameterList = parseParameterList(null); immutable StorageClass stc = parsePostfix(STC.undefined_, null); - immutable bool ambiguous = requireCallable && tf !is null; + ambiguous = requireCallable && tf !is null; tf = new AST.TypeFunction(parameterList, t, linkage, stc); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild | STC.return_)) { @@ -4017,83 +4050,140 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer : new AST.TypePointer(tf); // pointer to function if (ambiguous) { - // seek next inner function - auto tt = (cast(AST.TypeNext)t).syntaxCopy; - for ( - {AST.Type tnextt = (cast(AST.TypeFunction)tt.next).next; AST.TypeNext tnextn; } - (tnextn = tnextt.isTypeNext) !is null; - tnextt = tnextn.next - ) + static AST.TypeNext applyToNextInnerFunction(AST.Type t, bool isRef, LINK link = LINK.default_) { - if (tnextn.ty == Tfunction) + auto tt = (cast(AST.TypeNext) t).syntaxCopy; + for ( + {AST.Type tnextt = (cast(AST.TypeFunction)tt.next).next; AST.TypeNext tnextn; } + (tnextn = tnextt.isTypeNext) !is null; + tnextt = tnextn.next + ) { - auto tfn = cast(AST.TypeFunction)tnextn; - if (isRef) tfn.isref = true; - if (linkageSpecified) tfn.linkage = link; - goto Lfound; + if (tnextn.ty == Tfunction) + { + auto tfn = cast(AST.TypeFunction)tnextn; + tfn.isref = isRef; + tfn.linkage = link; + return tt; + } } + assert(0); } - assert(0); - Lfound: - if (isRef && linkageSpecified) + + // The negations of these should be logically impossible: + assert(intro.link || intro.isRef || !intro.link2); // second linkage with no first linkage and first ref + assert(intro.isRef || intro.link2 || !intro.isRef2); // second ref with no first ref and second linkage + + if (intro.link && intro.link2 || intro.isRef && intro.isRef2 || intro.isRef && intro.link2) { - error("Linkage and `ref` could refer to more than one `function` or `delegate` here."); - eSink.errorSupplemental(token.loc, "Suggested clarifying parentheses:"); - eSink.errorSupplemental(token.loc, " `extern (%s) ref %s` (possibly in parentheses)", AST.linkageToChars(link), t.toChars()); - eSink.errorSupplemental(token.loc, "or `%s`", tt.toChars()); + // If there are two linkages or two `ref` (or both) or `ref` and linkage after, presume user intent is clear, + // the user just forgot to use parentheses. + // Examples: + // - `extern(C) extern(C) int function() function()` + // - `ref ref int function() function()` + // - `ref extern(C) int function() function()` + // Notably absent: `extern(C) ref int function() function()` -- This falls into a different category + if (intro.link && intro.link2) error("Second linkage requires explicit parentheses."); + else if (intro.isRef && intro.isRef2) error("Second `ref` requires explicit parentheses."); + else error("Linkage after `ref` requires explicit parentheses."); + if (intro.link != LINK.default_) + eSink.errorSupplemental(token.loc, "Use `extern(%s)%s %s`", AST.linkageToChars(intro.link), (intro.isRef ? " ref" : "").ptr, applyToNextInnerFunction(t, intro.isRef2, intro.link2).toChars()); + else + eSink.errorSupplemental(token.loc, "Use `%s%s`", (intro.isRef ? "ref " : "").ptr, applyToNextInnerFunction(t, intro.isRef2, intro.link2).toChars()); } - else if (isRef) + else if (intro.link) { - error("`ref` could refer to more than one `function` or `delegate` here."); + // Examples: + // - `extern(C) ref int function() function()` + // - `extern(C) int function() function()` + error("Linkage%s could refer to more than one `function` or `delegate` here.", (intro.isRef ? " and `ref`" : "").ptr); eSink.errorSupplemental(token.loc, "Suggested clarifying parentheses:"); - eSink.errorSupplemental(token.loc, " `ref %s` (possibly in parentheses)", t.toChars()); - eSink.errorSupplemental(token.loc, "or `%s`", tt.toChars()); + eSink.errorSupplemental(token.loc, " `extern(%s)%s %s` (possibly in parentheses)", AST.linkageToChars(intro.link), (intro.isRef ? " ref" : "").ptr, t.toChars()); + if (intro.isRef) + { + eSink.errorSupplemental(token.loc, "or `extern(%s) %s`", AST.linkageToChars(intro.link), applyToNextInnerFunction(t, true).toChars()); + } + eSink.errorSupplemental(token.loc, "or `%s`", applyToNextInnerFunction(t, intro.isRef, intro.link).toChars()); } - else if (linkageSpecified) + else { - error("Linkage could refer to more than one `function` or `delegate` here."); + // Example: + // - `ref int function() function()` + assert(intro.isRef); + error("`ref` could refer to more than one `function` or `delegate` here."); eSink.errorSupplemental(token.loc, "Suggested clarifying parentheses:"); - eSink.errorSupplemental(token.loc, " `extern (%s) %s` (possibly in parentheses)", AST.linkageToChars(link), t.toChars()); - eSink.errorSupplemental(token.loc, "or `%s`", tt.toChars()); + eSink.errorSupplemental(token.loc, " `ref %s` (possibly in parentheses)", t.toChars()); + eSink.errorSupplemental(token.loc, "or `%s`", applyToNextInnerFunction(t, true).toChars()); } } continue; } default: { - if (requireCallable) + if (requireCallable && !ambiguous) { if (tf is null) { - if (linkageSpecified && isRef) error("Linkage and `ref` are only valid for `function` and `delegate` types"); - else if (isRef) + if (intro.linkageSpecified && intro.isRef) error("Linkage and `ref` are only valid for `function` and `delegate` types"); + else if (intro.isRef) { error("`ref` is not a type qualifier."); - eSink.errorSupplemental(token.loc, "To form a basic type, it must be followed by a function pointer or delegate suffix."); + eSink.errorSupplemental(token.loc, "It is only valid in this context to form a function pointer or delegate type,"); + eSink.errorSupplemental(token.loc, "but no `function(...)` or `delegate(...)` suffix was found."); } else error("Linkage is only valid for `function` and `delegate` types"); } + + // Handle errors. + // The negations of these should be logically impossible: + assert(intro.link || intro.isRef || !intro.link2); // second linkage with no first linkage and first ref + assert(intro.isRef || intro.link2 || !intro.isRef2); // second ref with no first ref and second linkage + if (!intro.link && intro.isRef && intro.link2 && !intro.isRef2) + { + // Assume the user accidentally flipped `ref` and linkage + // Example: `ref extern(C) int function()` + error("Linkage must come before `ref`."); + eSink.errorSupplemental(token.loc, "Use `extern(%s) ref %s`", AST.linkageToChars(intro.link2), t.toChars()); + } + else if (intro.link2 || intro.isRef2) + { + // Examples: + // - `ref ref int function()` + // - `ref extern(C) int function()` + // - `ref extern(C) ref int function()` + // - `extern(C) extern(C) int function()` + // - `extern(C) extern(C) ref int function()` + // - `extern(C) ref ref int function()` + // - `extern(C) ref extern(C) int function()` + // - `extern(C) ref extern(C) ref int function()` + assert(intro.link || intro.isRef); + immutable dupLink = intro.link && intro.link2; + immutable dupRef = intro.isRef && intro.isRef2; + error("Duplicate %s%s%s", (dupLink ? "linkage" : "").ptr, (dupLink && dupRef ? " and " : "").ptr, (dupRef ? "`ref`" : "").ptr); + eSink.errorSupplemental(token.loc, "If a second `function(...)` or `delegate(...)` is missing,",); + eSink.errorSupplemental(token.loc, "parentheses around the inner function pointer or delegate type are needed.",); + } + tf.next = tf.next.addSTC(stc2); - if (isRef) tf.isref = true; - if (linkageSpecified) + tf.isref = intro.isRef || intro.isRef2; + if (intro.linkageSpecified) { - // tf.linkage = link; + tf.linkage = intro.link ? intro.link : intro.link2; // NOTE: The above should work, but does not. // Everything that follows is a workaround. // Idea: Replace tn by - // typeof(function{ extern() ) __result; return __result; }()) auto resultId = new Identifier("__result"); - auto loc_ = loc; - auto fd = new AST.FuncLiteralDeclaration(loc_, Loc.initial, new AST.TypeFunction(AST.ParameterList(), null, LINK.default_), TOK.function_, null); + auto fd = new AST.FuncLiteralDeclaration(token.loc, Loc.initial, new AST.TypeFunction(AST.ParameterList(), null, LINK.default_), TOK.function_, null); auto vardecl = new AST.Dsymbols(); - vardecl.push(new AST.VarDeclaration(loc_, tn, resultId, null)); - fd.fbody = new AST.CompoundStatement(loc_, - new AST.ExpStatement(loc_, new AST.DeclarationExp(loc_, new AST.LinkDeclaration(loc_, link, vardecl))), - new AST.ReturnStatement(loc_, new AST.IdentifierExp(loc_, resultId)) + vardecl.push(new AST.VarDeclaration(token.loc, tn, resultId, null)); + fd.fbody = new AST.CompoundStatement(token.loc, + new AST.ExpStatement(token.loc, new AST.DeclarationExp(token.loc, new AST.LinkDeclaration(token.loc, tf.linkage, vardecl))), + new AST.ReturnStatement(token.loc, new AST.IdentifierExp(token.loc, resultId)) ); - auto typeoftype = new AST.TypeTypeof(loc_, new AST.CallExp(loc_, new AST.FuncExp(loc_, fd))); + auto typeoftype = new AST.TypeTypeof(token.loc, new AST.CallExp(token.loc, new AST.FuncExp(token.loc, fd))); if (t is tn) return typeoftype; @@ -4132,10 +4222,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ private AST.Type parseDeclarator(AST.Type t, ref int palt, Identifier* pident, AST.TemplateParameters** tpl = null, StorageClass storageClass = 0, - bool* pdisable = null, AST.Expressions** pudas = null, bool isRefCallable = false, LINK link = LINK.default_) + bool* pdisable = null, AST.Expressions** pudas = null, CallableIntroducer intro = CallableIntroducer()) { //printf("parseDeclarator(tpl = %p)\n", tpl); - t = parseTypeSuffixes(t, storageClass, isRefCallable, link); + t = parseTypeSuffixes(t, storageClass, intro); AST.Type ts; switch (token.value) { @@ -5018,7 +5108,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("cannot put a storage-class in an `alias` declaration."); // parseAttributes shouldn't have set these variables assert(link == linkage && !setAlignment && ealign is null); - auto tpl_ = cast(AST.TemplateDeclaration) s; + auto tpl_ = s.isTemplateDeclaration; if (tpl_ is null || tpl_.members.length != 1) { error("user-defined attributes are not allowed on `alias` declarations"); @@ -5201,8 +5291,19 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer stc = STC.ref_; nextToken(); } - if (token.value != TOK.leftParenthesis && token.value != TOK.leftCurly && - token.value != TOK.goesTo) + if (token.value != TOK.leftParenthesis && token.value != TOK.leftCurly && token.value != TOK.goesTo + || token.value == TOK.leftParenthesis && { + size_t nesting = 1; + auto t = &token; + do + { + t = peek(t); + nesting += (t.value == TOK.leftParenthesis) - (t.value == TOK.rightParenthesis); + } + while (nesting > 0 || t.value != TOK.rightParenthesis); + return peek(t).value == TOK.leftParenthesis; + }() + ) { // function type (parameters) { statements... } // delegate type (parameters) { statements... } @@ -6402,17 +6503,13 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.scope_: if (peekNext() != TOK.leftParenthesis) goto Ldeclaration; // scope used as storage class - nextToken(); - check(TOK.leftParenthesis); - if (token.value != TOK.identifier) - { - error("scope identifier expected"); - goto Lerror; - } - else { - TOK t = TOK.onScopeExit; - Identifier id = token.ident; + Token* token2 = peek(peek(&token)); + + if (token2.value != TOK.identifier || peekNext3() != TOK.rightParenthesis) + goto Ldeclaration; // scope used as storage class + Identifier id = token2.ident; + TOK t; if (id == Id.exit) t = TOK.onScopeExit; else if (id == Id.failure) @@ -6420,9 +6517,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer else if (id == Id.success) t = TOK.onScopeSuccess; else - error("valid scope identifiers are `exit`, `failure`, or `success`, not `%s`", id.toChars()); + goto Ldeclaration; // scope used as storage class + nextToken(); + nextToken(); + nextToken(); nextToken(); - check(TOK.rightParenthesis); AST.Statement st = parseStatement(ParseStatementFlags.scope_); s = new AST.ScopeGuardStatement(loc, t, st); break; @@ -7584,52 +7683,60 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer t = peek(t); while (t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) t = peek(t); - bool hasLinkage = false; - if (t.value == TOK.extern_) + RequireCallable requireCallable = RequireCallable.no; + do { - t = peek(t); - if (t.value != TOK.leftParenthesis) - goto Lfalse; - t = peek(t); - if (t.value != TOK.identifier) goto Lfalse; - switch (t.ident.toString()) + if (t.value == TOK.extern_) { - case "D": - case "System": - case "Windows": t = peek(t); - if (t.value != TOK.rightParenthesis) goto Lfalse; - t = peek(t); - break; - - case "C": // C or C++ + if (t.value != TOK.leftParenthesis) + goto Lfalse; t = peek(t); - if (t.value == TOK.plusPlus) // C++ linkage + if (t.value != TOK.identifier) goto Lfalse; + switch (t.ident.toString()) { + case "D": + case "System": + case "Windows": + t = peek(t); + if (t.value != TOK.rightParenthesis) goto Lfalse; + t = peek(t); + break; + + case "C": // C or C++ + t = peek(t); + if (t.value == TOK.plusPlus) // C++ linkage + { + t = peek(t); + } + if (t.value != TOK.rightParenthesis) goto Lfalse; + t = peek(t); + break; + + case "Objective": // Objective-C + t = peek(t); + if (t.value != TOK.min) goto Lfalse; + t = peek(t); + if (t.value != TOK.identifier || t.ident.toString() != "C") goto Lfalse; + t = peek(t); + if (t.value != TOK.rightParenthesis) goto Lfalse; t = peek(t); + break; + + default: + goto Lfalse; } - if (t.value != TOK.rightParenthesis) goto Lfalse; - t = peek(t); - break; - case "Objective": // Objective-C - t = peek(t); - if (t.value != TOK.min) goto Lfalse; - t = peek(t); - if (t.value != TOK.identifier || t.ident.toString() != "C") goto Lfalse; - t = peek(t); - if (t.value != TOK.rightParenthesis) goto Lfalse; + requireCallable = RequireCallable.atLeastOne; + } + if (t.value == TOK.ref_) + { + requireCallable = RequireCallable.atLeastOne; t = peek(t); - break; - - default: - goto Lfalse; } - - hasLinkage = true; } - immutable bool isRef = t.value == TOK.ref_; - if (isRef) t = peek(t); + while (t.value == TOK.ref_ || t.value == TOK.extern_); + while (t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) t = peek(t); if (!isBasicType(&t)) @@ -7637,7 +7744,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer int haveId = 1; int haveTpl = 0; - if (!isDeclarator(&t, &haveId, &haveTpl, TOK.rightParenthesis, requireCallable: hasLinkage || isRef)) + if (!isDeclarator(&t, &haveId, &haveTpl, TOK.rightParenthesis, /*allowAltSyntax:*/true/*(default)*/, /*requireCallable:*/requireCallable)) goto Lfalse; if (t.value != TOK.rightParenthesis) goto Lfalse; @@ -7661,7 +7768,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return false; } - private bool isDeclarator(Token** pt, int* haveId, int* haveTpl, TOK endtok, bool allowAltSyntax = true, bool requireCallable = false) + enum RequireCallable + { + no, exactlyOne, atLeastOne + } + + private bool isDeclarator(Token** pt, int* haveId, int* haveTpl, TOK endtok, bool allowAltSyntax = true, RequireCallable requireCallable = RequireCallable.no) { // This code parallels parseDeclarator() Token* t = *pt; @@ -7780,7 +7892,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer break; } - if (requireCallable && callables != 1) return false; + if (requireCallable == RequireCallable.exactlyOne && callables != 1) + { + return false; + } while (1) { @@ -9805,7 +9920,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } const stc = parseTypeCtor(); - const LINK link = () { + LINK getLinkage() + { if (token.value != TOK.extern_) return LINK.default_; auto l = parseLinkage(); @@ -9815,13 +9931,15 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (l.idents != null || l.identExps != null) error("C++ namespaces not allowed here"); return l.link; - }(); + }; + immutable link = getLinkage(); // Handle `ref` TypeCtors(opt) BasicType TypeSuffixes(opt) CallableSuffix NonCallableSuffixes(opt) const bool isRef = token.value == TOK.ref_; if (isRef) nextToken(); + immutable link2 = getLinkage(); const stc2 = parseTypeCtor(); auto t = parseBasicType(true); - t = parseTypeSuffixes(t, stc2, isRef, link); + t = parseTypeSuffixes(t, stc2, CallableIntroducer(link, link2, isRef)); t = t.addSTC(stc); if (t.ty == Taarray) { From 8f968e55fe4d88be6a1f72ce694866a647e09e1d Mon Sep 17 00:00:00 2001 From: "Quirin F. Schroll" Date: Mon, 23 Dec 2024 02:04:18 +0100 Subject: [PATCH 23/27] Implement the Scope Guard Compromise --- compiler/src/dmd/parse.d | 55 ++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 366370df0d46..2d9c10dbe5e4 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -6501,15 +6501,56 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer goto Lerror; case TOK.scope_: + // The `scope` keyword can introduce: + // 1. a scope guard via: + // 1.1 Simple scope guard `scope (token) NonEmptyOrScopeBlockStatement` + // 1.2 Elaborate scope guard `scope (token tokens..) ScopeBlockStatement` + // 2. a `scope` variable via: + // `scope (token tokens..)` followed by something other than a `ScopeBlockStatement` if (peekNext() != TOK.leftParenthesis) + { goto Ldeclaration; // scope used as storage class + } + { - Token* token2 = peek(peek(&token)); + Token* tokenAfterClosingParen = peek(peek(&token)); + size_t argumentLength = 0; + tokenAfterClosingParen = peek(tokenAfterClosingParen); + for (size_t level = 1; level != 0; ++argumentLength) + { + level += tokenAfterClosingParen.value == TOK.leftParenthesis; + level -= tokenAfterClosingParen.value == TOK.rightParenthesis; + tokenAfterClosingParen = peek(tokenAfterClosingParen); + if (tokenAfterClosingParen.value == TOK.endOfFile) + { + error("unmatched parenthesis"); + goto Lerror; + } + } - if (token2.value != TOK.identifier || peekNext3() != TOK.rightParenthesis) + if (argumentLength == 0) + { + error("expected type or scope guard after `scope`, not empty parentheses"); + goto Lerror; + } + + if (argumentLength > 1 && tokenAfterClosingParen.value != TOK.leftCurly) + { goto Ldeclaration; // scope used as storage class - Identifier id = token2.ident; - TOK t; + } + } + // Handle the scope guard + nextToken(); + nextToken(); + if (token.value != TOK.identifier) + { + error("unsupported scope guard"); + goto Lerror; + } + else + { + TOK t = TOK.onScopeExit; + Identifier id = token.ident; if (id == Id.exit) t = TOK.onScopeExit; else if (id == Id.failure) @@ -6517,11 +6558,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer else if (id == Id.success) t = TOK.onScopeSuccess; else - goto Ldeclaration; // scope used as storage class - nextToken(); - nextToken(); - nextToken(); + error("supported scope identifiers are `exit`, `failure`, or `success`, not `%s`", id.toChars()); nextToken(); + check(TOK.rightParenthesis); AST.Statement st = parseStatement(ParseStatementFlags.scope_); s = new AST.ScopeGuardStatement(loc, t, st); break; From 3418a1efa79b59e2a871c4db1a54b589aedf1760 Mon Sep 17 00:00:00 2001 From: "Quirin F. Schroll" Date: Thu, 16 Jan 2025 04:36:44 +0100 Subject: [PATCH 24/27] whitespace --- compiler/src/dmd/parse.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 5d84a04a9bc2..ac154409d48e 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -6535,7 +6535,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { goto Ldeclaration; // scope used as storage class } - + { Token* tokenAfterClosingParen = peek(peek(&token)); size_t argumentLength = 0; From 8a2b13659589a9cdfea91c8169c9b208419dc2bc Mon Sep 17 00:00:00 2001 From: "Quirin F. Schroll" Date: Thu, 16 Jan 2025 04:44:07 +0100 Subject: [PATCH 25/27] Older compiler issues --- compiler/src/dmd/parse.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index ac154409d48e..25947826052c 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -7552,7 +7552,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { int haveId = 0; int haveTpl = 0; - const bool isDecl = isDeclarator(&t, &haveId, &haveTpl, endtok, allowAltSyntax: anyStorageClass || needId != NeedDeclaratorId.mustIfDstyle); + const bool isDecl = isDeclarator(&t, &haveId, &haveTpl, endtok, anyStorageClass || needId != NeedDeclaratorId.mustIfDstyle); // printf("isDeclaration() (%d,%d) isDeclarator: %d\n", t.loc.linnum, t.loc.charnum, isDecl); if (!isDecl) goto Lisnot; @@ -10025,7 +10025,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (l.idents != null || l.identExps != null) error("C++ namespaces not allowed here"); return l.link; - }; + } immutable link = getLinkage(); // Handle `ref` TypeCtors(opt) BasicType TypeSuffixes(opt) CallableSuffix NonCallableSuffixes(opt) const bool isRef = token.value == TOK.ref_; From ac534974c7fcecce5fa4d1420da4522341deff3f Mon Sep 17 00:00:00 2001 From: "Quirin F. Schroll" Date: Mon, 7 Jul 2025 19:40:22 +0200 Subject: [PATCH 26/27] Add compilable example --- .../test/compilable/scopeguard_reffunctype.d | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 compiler/test/compilable/scopeguard_reffunctype.d diff --git a/compiler/test/compilable/scopeguard_reffunctype.d b/compiler/test/compilable/scopeguard_reffunctype.d new file mode 100644 index 000000000000..453bb9e630c5 --- /dev/null +++ b/compiler/test/compilable/scopeguard_reffunctype.d @@ -0,0 +1,20 @@ +// REQUIRED_ARGS: -preview=dip1000 + +struct exit +{ + int x; + + ref int foo() return @safe => x; +} + +void main() @nogc @safe +{ + exit obj; + + // scope variable: + scope (ref int delegate(int x) @safe) dg = ref(int x) => obj.foo = x; + // Note: `scope` is needed so `main` is `@nogc` + + // scope guard: + scope(exit) dg = null; +} From 9796a0087dadc2e86022e8c9fbcf50f85b3a8a87 Mon Sep 17 00:00:00 2001 From: "Quirin F. Schroll" Date: Mon, 7 Jul 2025 20:35:39 +0200 Subject: [PATCH 27/27] Fix problem with scope plus empty parens --- compiler/src/dmd/parse.d | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index b53eee02256a..05744986eedd 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -6610,19 +6610,17 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } { - Token* tokenAfterClosingParen = peek(peek(&token)); - size_t argumentLength = 0; - tokenAfterClosingParen = peek(tokenAfterClosingParen); - for (size_t level = 1; level != 0; ++argumentLength) + Token* t = peek(peek(&token)); + size_t argumentLength = -1; + for (size_t level = 1; level != 0; ++argumentLength, t = peek(t)) { - level += tokenAfterClosingParen.value == TOK.leftParenthesis; - level -= tokenAfterClosingParen.value == TOK.rightParenthesis; - tokenAfterClosingParen = peek(tokenAfterClosingParen); - if (tokenAfterClosingParen.value == TOK.endOfFile) + if (t.value == TOK.endOfFile) { error("unmatched parenthesis"); goto Lerror; } + + level += (t.value == TOK.leftParenthesis) - (t.value == TOK.rightParenthesis); } if (argumentLength == 0) @@ -6630,8 +6628,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("expected type or scope guard after `scope`, not empty parentheses"); goto Lerror; } - - if (argumentLength > 1 && tokenAfterClosingParen.value != TOK.leftCurly) + if (argumentLength > 1 && t.value != TOK.leftCurly) { goto Ldeclaration; // scope used as storage class } @@ -7057,8 +7054,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer goto Lerror; Lerror: - while (token.value != TOK.rightCurly && token.value != TOK.semicolon && token.value != TOK.endOfFile) + int nesting = 0; + while (nesting > 0 || token.value != TOK.rightCurly && token.value != TOK.semicolon && token.value != TOK.endOfFile) + { + nesting += (token.value == TOK.leftCurly) - (token.value == TOK.rightCurly); nextToken(); + } if (token.value == TOK.semicolon) nextToken(); s = new AST.ErrorStatement;