diff --git a/VERSION b/VERSION
index 67e32f8d7ef1..46eabdfde324 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v2.112.0-beta.1
+v2.112.0-beta.1+PrimaryTypeSyntax
diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d
index 53fc2558c748..f56a4b3ab6ae 100644
--- a/compiler/src/dmd/dsymbolsem.d
+++ b/compiler/src/dmd/dsymbolsem.d
@@ -1072,7 +1072,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor
if ((dsym.storage_class & (STC.ref_ | STC.field)) == (STC.ref_ | STC.field) && dsym.ident != Id.This)
{
- .error(dsym.loc, "%s `%s` - field declarations cannot be `ref`", dsym.kind, dsym.toPrettyChars);
+ .error(dsym.loc, "%s `%s` - field declarations cannot be `ref`; use parentheses for a function pointer or delegate type with `ref` return", dsym.kind, dsym.toPrettyChars);
}
if (dsym.type.hasWild())
diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d
index 6b9442892a84..c2135295d720 100644
--- a/compiler/src/dmd/hdrgen.d
+++ b/compiler/src/dmd/hdrgen.d
@@ -4016,21 +4016,56 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, ref O
return;
}
t.inuse++;
+ bool parenWritten = false;
+ void openParenthesis()
+ {
+ if (!parenWritten)
+ {
+ buf.put('(');
+ parenWritten = true;
+ }
+ }
if (t.linkage > LINK.d && hgs.ddoc != 1 && !hgs.hdrgen)
{
+ openParenthesis();
linkageToBuffer(buf, t.linkage);
buf.put(' ');
}
if (t.linkage == LINK.objc && isStatic)
- buf.write("static ");
+ buf.put("static ");
if (t.next)
{
+ if (t.isRef)
+ {
+ openParenthesis();
+ buf.put("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.put(' ');
}
else if (hgs.ddoc)
+ {
+ openParenthesis();
buf.put("auto ");
+ }
if (ident)
buf.put(ident);
parametersToBuffer(t.parameterList, buf, hgs);
@@ -4042,13 +4077,15 @@ private void visitFuncIdentWithPostfix(TypeFunction t, const char[] ident, ref O
MODtoBuffer(buf, t.mod);
}
- void dg(string str)
+ void writeAttribute(string str)
{
+ if (str == "ref") return; // 'ref' is handled above
buf.put(' ');
buf.put(str);
}
- t.attributesApply(&dg);
-
+ t.attributesApply(&writeAttribute);
+ if (parenWritten)
+ buf.put(')');
t.inuse--;
}
diff --git a/compiler/src/dmd/lexer.d b/compiler/src/dmd/lexer.d
index 62855fe93f93..d991638ca5ac 100644
--- a/compiler/src/dmd/lexer.d
+++ b/compiler/src/dmd/lexer.d
@@ -312,6 +312,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/mtype.d b/compiler/src/dmd/mtype.d
index 6965d12e213d..71a618d2e6d1 100644
--- a/compiler/src/dmd/mtype.d
+++ b/compiler/src/dmd/mtype.d
@@ -1521,6 +1521,28 @@ extern (C++) abstract class Type : ASTNode
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;
+ }
+ }
+
extern (D) bool isStaticOrDynamicArray() const { return ty == Tarray || ty == Tsarray; }
}
@@ -1594,6 +1616,8 @@ extern (C++) abstract class TypeNext : Type
this.next = next;
}
+ abstract override TypeNext syntaxCopy();
+
override final int hasWild() const
{
if (ty == Tfunction)
@@ -2152,6 +2176,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 e9099d550bf1..05744986eedd 100644
--- a/compiler/src/dmd/parse.d
+++ b/compiler/src/dmd/parse.d
@@ -443,6 +443,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)
@@ -3581,6 +3582,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
@@ -3589,55 +3605,49 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
*/
AST.Type parseType(Identifier* pident = null, AST.TemplateParameters** ptpl = null, Loc* pdeclLoc = null)
{
- /* 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
- */
- STC stc = STC.none;
- while (1)
- {
- switch (token.value)
+ //printf("parseType()\n");
+ immutable stc = parseTypeCtor();
+
+ // 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()
{
- case TOK.const_:
- if (peekNext() == TOK.leftParenthesis)
- break; // const as type constructor
- stc |= STC.const_; // const as storage class
- nextToken();
- continue;
+ if (token.value != TOK.extern_) return LINK.default_;
- case TOK.immutable_:
- if (peekNext() == TOK.leftParenthesis)
- break;
- stc |= STC.immutable_;
- nextToken();
- continue;
+ 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();
- case TOK.shared_:
- if (peekNext() == TOK.leftParenthesis)
- break;
- stc |= STC.shared_;
+ // Handle `ref` TypeCtors(opt) BasicType TypeSuffixes(opt) CallableSuffix NonCallableSuffixes(opt)
+ result.isRef = token.value == TOK.ref_;
+ if (result.isRef)
+ {
nextToken();
- continue;
+ }
- case TOK.inout_:
- if (peekNext() == TOK.leftParenthesis)
- break;
- stc |= STC.wild;
- nextToken();
- continue;
+ result.link2 = getLinkage();
- default:
- break;
+ result.isRef2 = token.value == TOK.ref_;
+ if (result.isRef2)
+ {
+ nextToken();
}
- break;
- }
+
+ return result;
+ }();
+
+ immutable stc2 = parseTypeCtor();
const typeLoc = token.loc;
@@ -3647,7 +3657,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
if (pdeclLoc)
*pdeclLoc = token.loc;
int alt = 0;
- t = parseDeclarator(t, alt, pident, ptpl);
+ t = parseDeclarator(t, alt, pident, ptpl, stc2, null, null, intro);
checkCstyleTypeSyntax(typeLoc, t, alt, pident ? *pident : null);
t = t.addSTC(stc);
@@ -3857,6 +3867,13 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
check(TOK.rightParenthesis);
break;
+ case TOK.leftParenthesis:
+ // (type)
+ nextToken();
+ t = parseType();
+ check(TOK.rightParenthesis);
+ break;
+
default:
error("basic type expected, not `%s`", token.toChars());
if (token.value == TOK.else_)
@@ -4017,9 +4034,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, immutable STC stc2 = STC.none, immutable CallableIntroducer intro = CallableIntroducer())
{
//printf("parseTypeSuffixes()\n");
+ 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)
{
switch (token.value)
@@ -4076,28 +4097,178 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.delegate_:
case TOK.function_:
{
- // Handle delegate declaration:
- // t delegate(parameter list) nothrow pure
- // t function(parameter list) nothrow pure
- const save = token.value;
+ // 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
+ immutable callableKeyword = token.value;
nextToken();
auto parameterList = parseParameterList(null);
- STC stc = parsePostfix(STC.none, null);
- auto tf = new AST.TypeFunction(parameterList, t, linkage, stc);
+ immutable STC stc = parsePostfix(STC.none, 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_))
{
- 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
+ t = tn = callableKeyword == TOK.delegate_
+ ? new AST.TypeDelegate(tf)
+ : new AST.TypePointer(tf); // pointer to function
+ if (ambiguous)
+ {
+ static AST.TypeNext applyToNextInnerFunction(AST.Type t, bool isRef, LINK link = LINK.default_)
+ {
+ 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
+ )
+ {
+ if (tnextn.ty == Tfunction)
+ {
+ auto tfn = cast(AST.TypeFunction)tnextn;
+ tfn.isRef = isRef;
+ tfn.linkage = link;
+ return tt;
+ }
+ }
+ assert(0);
+ }
+
+ // 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)
+ {
+ // 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 (intro.link)
+ {
+ // 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, " `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
+ {
+ // 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, " `ref %s` (possibly in parentheses)", t.toChars());
+ eSink.errorSupplemental(token.loc, "or `%s`", applyToNextInnerFunction(t, true).toChars());
+ }
+ }
continue;
}
default:
- return t;
+ {
+ if (requireCallable && !ambiguous)
+ {
+ if (tf is null)
+ {
+ 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, "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);
+ tf.isRef = intro.isRef || intro.isRef2;
+ if (intro.linkageSpecified)
+ {
+ 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 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(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(token.loc, new AST.CallExp(token.loc, new AST.FuncExp(token.loc, fd)));
+
+ if (t is tn) return typeoftype;
+
+ AST.TypeNext tx = t.isTypeNext();
+ assert(tx !is null);
+ while (tx.next !is tn)
+ {
+ tx = tx.next.isTypeNext();
+ assert(tx !is null);
+ }
+ tx.next = typeoftype;
+ }
+ }
+ return t;
+ }
}
assert(0);
}
@@ -4121,10 +4292,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, STC storageClass = STC.none,
- bool* pdisable = null, AST.Expressions** pudas = null)
+ bool* pdisable = null, AST.Expressions** pudas = null, CallableIntroducer intro = CallableIntroducer())
{
//printf("parseDeclarator(tpl = %p)\n", tpl);
- t = parseTypeSuffixes(t);
+ t = parseTypeSuffixes(t, storageClass, intro);
AST.Type ts;
switch (token.value)
{
@@ -4419,7 +4590,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)
{
@@ -5025,7 +5196,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");
@@ -5041,6 +5212,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
{
@@ -5114,6 +5293,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);
@@ -5160,6 +5340,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
AST.TemplateParameters* tpl = null;
AST.ParameterList parameterList;
AST.Type tret = null;
+ LINK linkage = LINK.default_;
STC stc = STC.none;
TOK save = TOK.reserved;
@@ -5169,6 +5350,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();
@@ -5189,8 +5380,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... }
@@ -5277,6 +5479,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)
@@ -5772,7 +5975,6 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
{
switch (token.value)
{
- // parse ref for better error
case TOK.ref_:
stc = STC.ref_;
break;
@@ -5825,8 +6027,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
storageClass = appendStorageClass(storageClass, stc);
nextToken();
}
- auto n = peek(&token);
- if (storageClass != 0 && token.value == TOK.identifier && n.value == TOK.assign)
+ if (storageClass != 0 && token.value == TOK.identifier && peek(&token).value == TOK.assign)
{
Identifier ai = token.ident;
AST.Type at = null; // infer parameter type
@@ -5835,7 +6036,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
check(TOK.assign);
param = new AST.Parameter(aloc, storageClass, at, ai, null, null);
}
- else if (isDeclaration(&token, NeedDeclaratorId.must, TOK.assign, null))
+ else if (token.value == TOK.extern_ || isDeclaration(&token, NeedDeclaratorId.must, TOK.assign, null))
{
Identifier ai;
const aloc = token.loc;
@@ -5844,11 +6045,52 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
param = new AST.Parameter(aloc, storageClass, at, ai, null, null);
}
else if (storageClass != 0)
- error("found `%s` while expecting `=` or identifier", n.toChars());
+ error("found `%s` while expecting `=` or identifier", peek(&token).toChars());
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
@@ -5939,7 +6181,6 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.string_:
case TOK.interpolated:
case TOK.hexadecimalString:
- case TOK.leftParenthesis:
case TOK.cast_:
case TOK.mul:
case TOK.min:
@@ -6074,15 +6315,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`; BasicType can start with `(`.
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;
@@ -6153,6 +6393,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
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))
@@ -6352,13 +6598,47 @@ 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* t = peek(peek(&token));
+ size_t argumentLength = -1;
+ for (size_t level = 1; level != 0; ++argumentLength, t = peek(t))
+ {
+ if (t.value == TOK.endOfFile)
+ {
+ error("unmatched parenthesis");
+ goto Lerror;
+ }
+
+ level += (t.value == TOK.leftParenthesis) - (t.value == TOK.rightParenthesis);
+ }
+
+ if (argumentLength == 0)
+ {
+ error("expected type or scope guard after `scope`, not empty parentheses");
+ goto Lerror;
+ }
+ if (argumentLength > 1 && t.value != TOK.leftCurly)
+ {
+ goto Ldeclaration; // scope used as storage class
+ }
+ }
+ // Handle the scope guard
+ nextToken();
nextToken();
- check(TOK.leftParenthesis);
if (token.value != TOK.identifier)
{
- error("scope identifier expected");
+ error("unsupported scope guard");
goto Lerror;
}
else
@@ -6372,7 +6652,7 @@ 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());
+ error("supported scope identifiers are `exit`, `failure`, or `success`, not `%s`", id.toChars());
nextToken();
check(TOK.rightParenthesis);
AST.Statement st = parseStatement(ParseStatementFlags.scope_);
@@ -6774,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;
@@ -7257,6 +7541,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:
@@ -7269,45 +7570,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() { 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_)
{
- /* 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, 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;
@@ -7490,12 +7803,84 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
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))
+ while (t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_)
+ t = peek(t);
+ RequireCallable requireCallable = RequireCallable.no;
+ do
{
- goto Lfalse;
+ 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;
+ }
+
+ requireCallable = RequireCallable.atLeastOne;
+ }
+ if (t.value == TOK.ref_)
+ {
+ requireCallable = RequireCallable.atLeastOne;
+ 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))
+ goto Lfalse;
+
+ int haveId = 1;
+ int haveTpl = 0;
+ if (!isDeclarator(&t, &haveId, &haveTpl, TOK.rightParenthesis, /*allowAltSyntax:*/true/*(default)*/, /*requireCallable:*/requireCallable))
+ 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:
@@ -7510,11 +7895,17 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
return false;
}
- private bool isDeclarator(Token** pt, int* haveId, int* haveTpl, TOK endtok, bool allowAltSyntax = true)
+ 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;
bool parens;
+ uint callables = 0;
//printf("Parser::isDeclarator() %s\n", t.toChars());
if (t.value == TOK.assign)
@@ -7534,6 +7925,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))
@@ -7618,6 +8010,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
if (!isParameters(&t))
return false;
skipAttributes(t, &t);
+ ++callables;
continue;
default:
@@ -7626,6 +8019,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
break;
}
+ if (requireCallable == RequireCallable.exactlyOne && callables != 1)
+ {
+ return false;
+ }
+
while (1)
{
switch (t.value)
@@ -8656,9 +9054,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
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:
{
@@ -8723,6 +9121,33 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
{
AST.Dsymbol s = parseFunctionLiteral();
e = new AST.FuncExp(loc, 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;
+ 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_)
+ {
+ 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;
}
@@ -9633,8 +10058,26 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
}
const stc = parseTypeCtor();
+ 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;
+ }
+ 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);
+ t = parseTypeSuffixes(t, stc2, CallableIntroducer(link, link2, isRef));
t = t.addSTC(stc);
if (t.ty == Taarray)
{
@@ -9707,7 +10150,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/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;
+}
diff --git a/compiler/test/fail_compilation/fail21243.d b/compiler/test/fail_compilation/fail21243.d
index 0b4117d5bc63..59a66cb03250 100644
--- a/compiler/test/fail_compilation/fail21243.d
+++ b/compiler/test/fail_compilation/fail21243.d
@@ -1,14 +1,15 @@
/+ TEST_OUTPUT:
---
-fail_compilation/fail21243.d(12): Error: found `(` when expecting `ref` and function literal following `auto`
-fail_compilation/fail21243.d(12): Error: semicolon expected following auto declaration, not `int`
-fail_compilation/fail21243.d(12): Error: semicolon needed to end declaration of `x` instead of `)`
-fail_compilation/fail21243.d(12): Error: declaration expected, not `)`
-fail_compilation/fail21243.d(13): Error: `auto` can only be used as part of `auto ref` for function literal return values
-fail_compilation/fail21243.d(14): Error: `auto` can only be used as part of `auto ref` for function literal return values
-fail_compilation/fail21243.d(15): 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: `auto` can only be used as part of `auto ref` for function literal return values
+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/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 9f1f37763881..bb91f4de6c9d 100644
--- a/compiler/test/fail_compilation/issue16020.d
+++ b/compiler/test/fail_compilation/issue16020.d
@@ -1,16 +1,17 @@
/*
TEST_OUTPUT:
---
-fail_compilation/issue16020.d(14): Error: user-defined attributes not allowed for `alias` declarations
-fail_compilation/issue16020.d(15): Error: semicolon expected to close `alias` declaration, not `(`
-fail_compilation/issue16020.d(15): Error: semicolon needed to end declaration of `t` instead of `)`
-fail_compilation/issue16020.d(15): Error: declaration expected, not `)`
-fail_compilation/issue16020.d(16): Deprecation: storage class `final` has no effect in type aliases
+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: semicolon needed to end declaration of `t` instead of `)`
+fail_compilation/issue16020.d(2): Error: declaration expected, not `)`
+fail_compilation/issue16020.d(3): Deprecation: storage class `final` has no effect in type aliases
---
*/
module issue16020;
struct UDA{}
+#line 1
alias Fun = @UDA void();
alias FunTemplate = void(T)(T t);
alias F2 = final int();
diff --git a/compiler/test/fail_compilation/test21546.d b/compiler/test/fail_compilation/test21546.d
index 7a831ebaaebe..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() return ref` to `int delegate() return ref`
-fail_compilation/test21546.d(214): Error: cannot implicitly convert expression `dc` of type `const(int) delegate() return ref` to `immutable(int) delegate() return ref`
-fail_compilation/test21546.d(215): Error: cannot implicitly convert expression `di` of type `immutable(int) delegate() return ref` to `int delegate() return ref`
-fail_compilation/test21546.d(305): Error: cannot implicitly convert expression `[dgi]` of type `immutable(int) delegate() return ref[]` to `int delegate() return ref[]`
+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 991ae7bcc60f..eecd3b33b8b6 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/reffunctype.d b/compiler/test/runnable/reffunctype.d
new file mode 100644
index 000000000000..ef87cae76c0b
--- /dev/null
+++ b/compiler/test/runnable/reffunctype.d
@@ -0,0 +1,110 @@
+// REQUIRED_ARGS: -unittest -main
+
+// `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()) ) { }
+
+// `c` is `a` without clarifying 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 clarifying 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;
+}
+
+@safe unittest
+{
+ static int i = 0;
+ static ref int f() => i;
+ static assert(is(typeof(&f) == ref int function() nothrow @nogc @safe));
+
+ (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);
+}
+
+struct S
+{
+ int i;
+ ref int get() @safe return => i;
+}
+
+@safe unittest
+{
+ S s;
+ (ref int delegate() return @safe) dg = &s.get;
+ dg() = 1;
+ assert(s.i == 1);
+}
+
+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 const int function() @safe ));
+ static assert(!is( typeof(f) : ref immutable 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))) ));
+static assert((ref int function()).stringof == "(ref int function())");
+
+// 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 ) ));
diff --git a/compiler/test/runnable/testscope2.d b/compiler/test/runnable/testscope2.d
index 6c520e3196f8..d760eb2a11e9 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) return ref
-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))
---
*/