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)) --- */