Add named arguments to struct literals (#14776)

* Add named argument parsing

* Refactor StructInitializer semantic to make it reusable

* Move out `resolveStructLiteralNamedArgs`

* Resolve named args in struct literal

* Add tests

* Check for errors returned by `resolveStructLiteralNamedArgs`

* Expand names along with tuples

* Update error messages in tests

* Convert tabs to spaces in named arg test

* Fix style of `resolveStructLiteralNamedArgs`

* Clarify 'fixme' comment in expandTuples

* Add example to `expandTuples` documentation

* Update compiler/src/dmd/initsem.d

Co-authored-by: Razvan Nitu <razvan.nitu1305@gmail.com>

* Add overlapping initialization supplemental error to Struct Initializer

* Improve "too many initializers" error message

Co-authored-by: Razvan Nitu <razvan.nitu1305@gmail.com>
This commit is contained in:
Dennis 2023-01-17 13:08:30 +01:00 committed by GitHub
parent a15cf38f92
commit 42609ae98e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 329 additions and 141 deletions

View file

@ -249,18 +249,63 @@ bool isDotOpDispatch(Expression e)
}
/****************************************
* Expand tuples.
* Input:
* exps aray of Expressions
* Output:
* exps rewritten in place
* Expand tuples in-place.
*
* Example:
* When there's a call `f(10, pair: AliasSeq!(20, 30), single: 40))`, the input is:
* exps = [10, (20, 30), 40]
* names = [null, "pair", "single"]);
* The arrays will be modified to:
* exps = [10, 20, 30, 40]
* names = [null, "pair", null, "single"]);
*
* Params:
* exps = array of Expressions
* names = optional array of names corresponding to Expressions
*/
extern (C++) void expandTuples(Expressions* exps)
extern (C++) void expandTuples(Expressions* exps, Identifiers* names = null)
{
//printf("expandTuples()\n");
if (exps is null)
return;
if (names)
{
if (exps.length != names.length)
{
// Fixme: this branch should be removed when CallExp rewrites
// (UFCS/operator overloading) are fixed to take named arguments into account
names.setDim(exps.length);
foreach (i; 0..exps.length)
(*names)[i] = null;
}
if (exps.length != names.length)
{
printf("exps.length = %d, names.length = %d\n", cast(int) exps.length, cast(int) names.length);
printf("exps = %s, names = %s\n", exps.toChars(), names.toChars());
if (exps.length > 0)
printf("%s\n", (*exps)[0].loc.toChars());
assert(0);
}
}
// At `index`, a tuple of length `length` is expanded. Insert corresponding nulls in `names`.
void expandNames(size_t index, size_t length)
{
if (names)
{
if (length == 0)
{
names.remove(index);
return;
}
foreach (i; 1 .. length)
{
names.insert(index + i, cast(Identifier) null);
}
}
}
for (size_t i = 0; i < exps.length; i++)
{
Expression arg = (*exps)[i];
@ -275,6 +320,7 @@ extern (C++) void expandTuples(Expressions* exps)
if (!tt.arguments || tt.arguments.length == 0)
{
exps.remove(i);
expandNames(i, 0);
if (i == exps.length)
return;
}
@ -285,6 +331,7 @@ extern (C++) void expandTuples(Expressions* exps)
foreach (j, a; *tt.arguments)
(*texps)[j] = new TypeExp(e.loc, a.type);
exps.insert(i, texps);
expandNames(i, texps.length);
}
i--;
continue;
@ -297,6 +344,7 @@ extern (C++) void expandTuples(Expressions* exps)
TupleExp te = cast(TupleExp)arg;
exps.remove(i); // remove arg
exps.insert(i, te.exps); // replace with tuple contents
expandNames(i, te.exps.length);
if (i == exps.length)
return; // empty tuple, no more arguments
(*exps)[i] = Expression.combine(te.e0, (*exps)[i]);
@ -5079,16 +5127,18 @@ extern (C++) final class DotTypeExp : UnaExp
extern (C++) final class CallExp : UnaExp
{
Expressions* arguments; // function arguments
Identifiers* names; // named argument identifiers
FuncDeclaration f; // symbol to call
bool directcall; // true if a virtual call is devirtualized
bool inDebugStatement; /// true if this was in a debug statement
bool ignoreAttributes; /// don't enforce attributes (e.g. call @gc function in @nogc code)
VarDeclaration vthis2; // container for multi-context
extern (D) this(const ref Loc loc, Expression e, Expressions* exps)
extern (D) this(const ref Loc loc, Expression e, Expressions* exps, Identifiers* names = null)
{
super(loc, EXP.call, __traits(classInstanceSize, CallExp), e);
this.arguments = exps;
this.names = names;
}
extern (D) this(const ref Loc loc, Expression e)
@ -5155,7 +5205,7 @@ extern (C++) final class CallExp : UnaExp
override CallExp syntaxCopy()
{
return new CallExp(loc, e1.syntaxCopy(), arraySyntaxCopy(arguments));
return new CallExp(loc, e1.syntaxCopy(), arraySyntaxCopy(arguments), names);
}
override bool isLvalue()

View file

@ -45,7 +45,7 @@ typedef union tree_node Symbol;
struct Symbol; // back end symbol
#endif
void expandTuples(Expressions *exps);
void expandTuples(Expressions *exps, Identifiers *names = nullptr);
bool isTrivialExp(Expression *e);
bool hasSideEffect(Expression *e, bool assumeImpureCalls = false);
@ -826,6 +826,7 @@ class CallExp final : public UnaExp
{
public:
Expressions *arguments; // function arguments
Identifiers *names;
FuncDeclaration *f; // symbol to call
bool directcall; // true if a virtual call is devirtualized
bool inDebugStatement; // true if this was in a debug statement

View file

@ -651,6 +651,9 @@ private Expression resolveUFCS(Scope* sc, CallExp ce)
if (!ce.arguments)
ce.arguments = new Expressions();
ce.arguments.shift(eleft);
if (!ce.names)
ce.names = new Identifiers();
ce.names.shift(null);
return null;
}
@ -1629,12 +1632,12 @@ private Expression rewriteOpAssign(BinExp exp)
* Returns:
* true a semantic error occurred
*/
private bool preFunctionParameters(Scope* sc, Expressions* exps, const bool reportErrors = true)
private bool preFunctionParameters(Scope* sc, Identifiers* names, Expressions* exps, const bool reportErrors = true)
{
bool err = false;
if (exps)
{
expandTuples(exps);
expandTuples(exps, names);
for (size_t i = 0; i < exps.length; i++)
{
@ -3549,7 +3552,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
{
return setError();
}
if (preFunctionParameters(sc, exp.arguments))
if (preFunctionParameters(sc, /*names*/ null, exp.arguments))
{
return setError();
}
@ -4287,7 +4290,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
if (FuncExp fe = exp.e1.isFuncExp())
{
if (arrayExpressionSemantic(exp.arguments.peekSlice(), sc) ||
preFunctionParameters(sc, exp.arguments))
preFunctionParameters(sc, exp.names, exp.arguments))
return setError();
// Run e1 semantic even if arguments have any errors
@ -4526,7 +4529,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
return;
}
if (arrayExpressionSemantic(exp.arguments.peekSlice(), sc) ||
preFunctionParameters(sc, exp.arguments))
preFunctionParameters(sc, exp.names, exp.arguments))
return setError();
// Check for call operator overload
@ -4624,7 +4627,22 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
/* It's a struct literal
*/
Lx:
Expression e = new StructLiteralExp(exp.loc, sd, exp.arguments, exp.e1.type);
Expressions* resolvedArgs = exp.arguments;
if (exp.names)
{
resolvedArgs = resolveStructLiteralNamedArgs(sd, exp.e1.type, sc, exp.loc,
(*exp.names)[],
(size_t i, Type t) => (*exp.arguments)[i],
i => (*exp.arguments)[i].loc
);
if (!resolvedArgs)
{
result = ErrorExp.get();
return;
}
}
Expression e = new StructLiteralExp(exp.loc, sd, resolvedArgs, exp.e1.type);
e = e.expressionSemantic(sc);
result = e;
return;

View file

@ -6874,7 +6874,7 @@ public:
void accept(Visitor* v) override;
};
extern void expandTuples(Array<Expression* >* exps);
extern void expandTuples(Array<Expression* >* exps, Array<Identifier* >* names = nullptr);
struct UnionExp final
{
@ -7422,6 +7422,7 @@ class CallExp final : public UnaExp
{
public:
Array<Expression* >* arguments;
Array<Identifier* >* names;
FuncDeclaration* f;
bool directcall;
bool inDebugStatement;

View file

@ -135,111 +135,19 @@ extern(C++) Initializer initializerSemantic(Initializer init, Scope* sc, ref Typ
sd.size(i.loc);
if (sd.sizeok != Sizeok.done)
return err();
const nfields = sd.nonHiddenFields();
//expandTuples for non-identity arguments?
auto elements = new Expressions(nfields);
auto elems = (*elements)[];
foreach (ref elem; elems)
elem = null;
// Run semantic for explicitly given initializers
// TODO: this part is slightly different from StructLiteralExp::semantic.
bool errors = false;
size_t fieldi = 0;
foreach (j, id; i.field[])
{
if (id)
{
/* Determine `fieldi` that `id` matches
*/
Dsymbol s = sd.search(i.loc, id);
if (!s)
{
s = sd.search_correct(id);
const initLoc = i.value[j].loc;
if (s)
error(initLoc, "`%s` is not a member of `%s`, did you mean %s `%s`?", id.toChars(), sd.toChars(), s.kind(), s.toChars());
else
error(initLoc, "`%s` is not a member of `%s`", id.toChars(), sd.toChars());
return err();
}
s.checkDeprecated(i.loc, sc);
s = s.toAlias();
// Find out which field index `s` is
for (fieldi = 0; 1; fieldi++)
{
if (fieldi >= nfields)
{
error(i.loc, "`%s.%s` is not a per-instance initializable field", sd.toChars(), s.toChars());
return err();
}
if (s == sd.fields[fieldi])
break;
}
}
if (j >= nfields)
{
error(i.value[j].loc, "too many initializers for `%s`", sd.toChars());
return err();
}
VarDeclaration vd = sd.fields[fieldi];
if (elems[fieldi])
{
error(i.value[j].loc, "duplicate initializer for field `%s`", vd.toChars());
errors = true;
elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors
++fieldi;
continue;
}
// Check for @safe violations
if (vd.type.hasPointers)
{
if ((!t.alignment.isDefault() && t.alignment.get() < target.ptrsize ||
(vd.offset & (target.ptrsize - 1))))
{
if (sc.setUnsafe(false, i.value[j].loc,
"field `%s.%s` cannot assign to misaligned pointers in `@safe` code", sd, vd))
{
errors = true;
elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors
++fieldi;
continue;
}
}
}
// Check for overlapping initializations (can happen with unions)
foreach (k, v2; sd.fields[0 .. nfields])
{
if (vd.isOverlappedWith(v2) && elems[k])
{
error(elems[k].loc, "overlapping initialization for field `%s` and `%s`", v2.toChars(), vd.toChars());
errors = true;
continue;
}
}
// Convert initializer to Expression `ex`
assert(sc);
auto tm = vd.type.addMod(t.mod);
auto iz = i.value[j].initializerSemantic(sc, tm, needInterpret);
auto ex = iz.initializerToExpression(null, (sc.flags & SCOPE.Cfile) != 0);
if (ex.op == EXP.error)
{
errors = true;
elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors
++fieldi;
continue;
}
Expression getExp(size_t j, Type fieldType)
{
// Convert initializer to Expression `ex`
auto tm = fieldType.addMod(t.mod);
auto iz = i.value[j].initializerSemantic(sc, tm, needInterpret);
auto ex = iz.initializerToExpression(null, (sc.flags & SCOPE.Cfile) != 0);
if (ex.op != EXP.error)
i.value[j] = iz;
elems[fieldi] = doCopyOrMove(sc, ex);
++fieldi;
}
if (errors)
return ex;
}
auto elements = resolveStructLiteralNamedArgs(sd, t, sc, i.loc, i.field[], &getExp, (size_t j) => i.value[j].loc);
if (!elements)
return err();
// Make a StructLiteralExp out of elements[]
@ -1514,3 +1422,141 @@ private bool hasNonConstPointers(Expression e)
}
return false;
}
/**
Given the names and values of a `StructInitializer` or `CallExp`,
resolve it to a list of expressions to construct a `StructLiteralExp`.
Params:
sd = struct
t = type of struct (potentially including qualifiers such as `const` or `immutable`)
sc = scope of the expression initializing the struct
iloc = location of expression initializing the struct
names = identifiers passed in argument list, `null` entries for positional arguments
getExp = function that, given an index into `names` and destination type, returns the initializing expression
getLoc = function that, given an index into `names`, returns a location for error messages
Returns: list of expressions ordered to the struct's fields, or `null` on error
*/
Expressions* resolveStructLiteralNamedArgs(StructDeclaration sd, Type t, Scope* sc,
Loc iloc, Identifier[] names, scope Expression delegate(size_t i, Type fieldType) getExp,
scope Loc delegate(size_t i) getLoc
)
{
//expandTuples for non-identity arguments?
const nfields = sd.nonHiddenFields();
auto elements = new Expressions(nfields);
auto elems = (*elements)[];
foreach (ref elem; elems)
elem = null;
// Run semantic for explicitly given initializers
// TODO: this part is slightly different from StructLiteralExp::semantic.
bool errors = false;
size_t fieldi = 0;
foreach (j, id; names)
{
const argLoc = getLoc(j);
if (id)
{
// Determine `fieldi` that `id` matches
Dsymbol s = sd.search(iloc, id);
if (!s)
{
s = sd.search_correct(id);
if (s)
error(argLoc, "`%s` is not a member of `%s`, did you mean %s `%s`?", id.toChars(), sd.toChars(), s.kind(), s.toChars());
else
error(argLoc, "`%s` is not a member of `%s`", id.toChars(), sd.toChars());
return null;
}
s.checkDeprecated(iloc, sc);
s = s.toAlias();
// Find out which field index `s` is
for (fieldi = 0; 1; fieldi++)
{
if (fieldi >= nfields)
{
error(iloc, "`%s.%s` is not a per-instance initializable field", sd.toChars(), s.toChars());
return null;
}
if (s == sd.fields[fieldi])
break;
}
}
if (nfields == 0)
{
error(argLoc, "initializer provided for struct `%s` with no fields", sd.toChars());
return null;
}
if (j >= nfields)
{
error(argLoc, "too many initializers for `%s` with %d field%s", sd.toChars(),
cast(int) nfields, nfields != 1 ? "s".ptr : "".ptr);
return null;
}
VarDeclaration vd = sd.fields[fieldi];
if (elems[fieldi])
{
error(argLoc, "duplicate initializer for field `%s`", vd.toChars());
errors = true;
elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors
++fieldi;
continue;
}
// Check for @safe violations
if (vd.type.hasPointers)
{
if ((!t.alignment.isDefault() && t.alignment.get() < target.ptrsize ||
(vd.offset & (target.ptrsize - 1))))
{
if (sc.setUnsafe(false, argLoc,
"field `%s.%s` cannot assign to misaligned pointers in `@safe` code", sd, vd))
{
errors = true;
elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors
++fieldi;
continue;
}
}
}
// Check for overlapping initializations (can happen with unions)
foreach (k, v2; sd.fields[0 .. nfields])
{
if (vd.isOverlappedWith(v2) && elems[k])
{
error(elems[k].loc, "overlapping initialization for field `%s` and `%s`", v2.toChars(), vd.toChars());
enum errorMsg = "`struct` initializers that contain anonymous unions" ~
" must initialize only the first member of a `union`. All subsequent" ~
" non-overlapping fields are default initialized";
if (!sd.isUnionDeclaration())
.errorSupplemental(elems[k].loc, errorMsg);
errors = true;
continue;
}
}
assert(sc);
auto ex = getExp(j, vd.type);
if (ex.op == EXP.error)
{
errors = true;
elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors
++fieldi;
continue;
}
elems[fieldi] = doCopyOrMove(sc, ex);
++fieldi;
}
if (errors)
return null;
return elements;
}

View file

@ -1285,7 +1285,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
if (token.value == TOK.leftParenthesis)
{
const loc = token.loc;
exp = new AST.CallExp(loc, exp, parseArguments());
AST.Expressions* args = new AST.Expressions();
AST.Identifiers* names = new AST.Identifiers();
parseNamedArguments(args, names);
exp = new AST.CallExp(loc, exp, args, names);
}
if (udas is null)
@ -8912,7 +8915,10 @@ LagainStc:
break;
case TOK.leftParenthesis:
e = new AST.CallExp(loc, e, parseArguments());
AST.Expressions* args = new AST.Expressions();
AST.Identifiers* names = new AST.Identifiers();
parseNamedArguments(args, names);
e = new AST.CallExp(loc, e, args, names);
continue;
case TOK.leftBracket:
@ -9347,26 +9353,52 @@ LagainStc:
private AST.Expressions* parseArguments()
{
// function call
AST.Expressions* arguments;
AST.Expressions* arguments = new AST.Expressions();
parseNamedArguments(arguments, null);
return arguments;
}
/*************************
* Collect argument list.
* Assume current token is ',', '$(LPAREN)' or '['.
*/
private void parseNamedArguments(AST.Expressions* arguments, AST.Identifiers* names)
{
assert(arguments);
arguments = new AST.Expressions();
const endtok = token.value == TOK.leftBracket ? TOK.rightBracket : TOK.rightParenthesis;
nextToken();
while (token.value != endtok && token.value != TOK.endOfFile)
{
if (peekNext() == TOK.colon)
{
// Named argument `name: exp`
auto loc = token.loc;
auto ident = token.ident;
check(TOK.identifier);
check(TOK.colon);
if (names)
names.push(ident);
else
error(loc, "named arguments not allowed here");
}
else
{
if (names)
names.push(null);
}
auto arg = parseAssignExp();
arguments.push(arg);
if (token.value != TOK.comma)
break;
nextToken(); //comma
}
check(endtok);
return arguments;
}
/*******************************************

View file

@ -0,0 +1,24 @@
struct S
{
string name;
int x;
int y;
}
immutable S s = S(x: 2, 3, name: "boo");
static assert(s.x == 2);
static assert(s.y == 3);
static assert(s.name == "boo");
union U
{
float f;
int i;
}
immutable U u = U(i: 2);
static assert(u.i == 2);

View file

@ -1,7 +1,8 @@
/*
TEST_OUTPUT:
---
fail_compilation/diag11132.d(22): Error: overlapping initialization for field `a` and `b`
fail_compilation/diag11132.d(23): Error: overlapping initialization for field `a` and `b`
fail_compilation/diag11132.d(23): `struct` initializers that contain anonymous unions must initialize only the first member of a `union`. All subsequent non-overlapping fields are default initialized
---
*/

View file

@ -1,7 +1,7 @@
/*
TEST_OUTPUT:
---
fail_compilation/fail155.d(20): Error: overlapping initialization for `y`
fail_compilation/fail155.d(20): Error: overlapping initialization for field `x` and `y`
fail_compilation/fail155.d(20): `struct` initializers that contain anonymous unions must initialize only the first member of a `union`. All subsequent non-overlapping fields are default initialized
---
*/

View file

@ -2,9 +2,9 @@
/*
TEST_OUTPUT:
---
fail_compilation/fail156.d(35): Error: overlapping initialization for `y`
fail_compilation/fail156.d(35): Error: overlapping initialization for field `x` and `y`
fail_compilation/fail156.d(35): `struct` initializers that contain anonymous unions must initialize only the first member of a `union`. All subsequent non-overlapping fields are default initialized
fail_compilation/fail156.d(42): Error: overlapping initialization for `y`
fail_compilation/fail156.d(42): Error: overlapping initialization for field `x` and `y`
fail_compilation/fail156.d(42): `struct` initializers that contain anonymous unions must initialize only the first member of a `union`. All subsequent non-overlapping fields are default initialized
---
*/

View file

@ -1,7 +1,7 @@
/*
TEST_OUTPUT:
---
fail_compilation/fail158.d(17): Error: more initializers than fields (2) of `S`
fail_compilation/fail158.d(17): Error: too many initializers for `S` with 2 fields
---
*/

View file

@ -3,8 +3,8 @@
/*
TEST_OUTPUT:
---
fail_compilation/fail22570.d(19): Error: more initializers than fields (1) of `S`
fail_compilation/fail22570.d(20): Error: more initializers than fields (1) of `S`
fail_compilation/fail22570.d(19): Error: too many initializers for `S` with 1 field
fail_compilation/fail22570.d(20): Error: too many initializers for `S` with 1 field
---
*/

View file

@ -1,7 +1,7 @@
/*
TEST_OUTPUT:
---
fail_compilation/fail299.d(14): Error: more initializers than fields (0) of `Foo`
fail_compilation/fail299.d(14): Error: initializer provided for struct `Foo` with no fields
---
*/

View file

@ -0,0 +1,15 @@
/**
TEST_OUTPUT:
---
fail_compilation/named_arguments_parse.d(10): Error: named arguments not allowed here
fail_compilation/named_arguments_parse.d(13): Error: named arguments not allowed here
fail_compilation/named_arguments_parse.d(14): Error: named arguments not allowed here
---
*/
@(attribute: 3)
void main()
{
mixin(thecode: "{}");
pragma(msg, themsg: "hello");
}

View file

@ -6,7 +6,7 @@ TEST_OUTPUT:
fail_compilation/test20998.d(76): Error: undefined identifier `invalid`
X x = { invalid, 2, "asd" };
^
fail_compilation/test20998.d(76): Error: too many initializers for `X`
fail_compilation/test20998.d(76): Error: too many initializers for `X` with 2 fields
X x = { invalid, 2, "asd" };
^
fail_compilation/test20998.d(83): Error: cannot implicitly convert expression `"a"` of type `string` to `int`
@ -15,7 +15,7 @@ X2 x2 = { ptr: null, "a", ptr: 2, 444 };
fail_compilation/test20998.d(83): Error: duplicate initializer for field `ptr`
X2 x2 = { ptr: null, "a", ptr: 2, 444 };
^
fail_compilation/test20998.d(83): Error: too many initializers for `X2`
fail_compilation/test20998.d(83): Error: too many initializers for `X2` with 3 fields
X2 x2 = { ptr: null, "a", ptr: 2, 444 };
^
fail_compilation/test20998.d(90): Error: overlapping initialization for field `ptr` and `x`
@ -27,7 +27,7 @@ X3 x3 = { ptr: null, "a", ptr: 2, 444 };
fail_compilation/test20998.d(90): Error: duplicate initializer for field `ptr`
X3 x3 = { ptr: null, "a", ptr: 2, 444 };
^
fail_compilation/test20998.d(90): Error: too many initializers for `X3`
fail_compilation/test20998.d(90): Error: too many initializers for `X3` with 3 fields
X3 x3 = { ptr: null, "a", ptr: 2, 444 };
^
fail_compilation/test20998.d(98): Error: field `X4.ptr` cannot assign to misaligned pointers in `@safe` code
@ -36,7 +36,7 @@ fail_compilation/test20998.d(98): Error: field `X4.ptr` cannot assign to misalig
fail_compilation/test20998.d(98): Error: cannot implicitly convert expression `"a"` of type `string` to `int`
X4 x4 = { ptr: null, "a", 444, ptr: 2, true };
^
fail_compilation/test20998.d(98): Error: too many initializers for `X4`
fail_compilation/test20998.d(98): Error: too many initializers for `X4` with 2 fields
X4 x4 = { ptr: null, "a", 444, ptr: 2, true };
^
fail_compilation/test20998.d(102): called from here: `test()`
@ -51,16 +51,16 @@ X2 a5 = { ptr: 1, ptr: 2, ptr: 444, ptr: 555 };
fail_compilation/test20998.d(104): Error: duplicate initializer for field `ptr`
X2 a5 = { ptr: 1, ptr: 2, ptr: 444, ptr: 555 };
^
fail_compilation/test20998.d(104): Error: too many initializers for `X2`
fail_compilation/test20998.d(104): Error: too many initializers for `X2` with 3 fields
X2 a5 = { ptr: 1, ptr: 2, ptr: 444, ptr: 555 };
^
fail_compilation/test20998.d(107): Error: too many initializers for `X2`
fail_compilation/test20998.d(107): Error: too many initializers for `X2` with 3 fields
X2 c6 = { null, 2, true, null };
^
fail_compilation/test20998.d(116): Error: cannot implicitly convert expression `1` of type `int` to `immutable(char*)`
immutable Struct iStruct = {1, &ch};
^
fail_compilation/test20998.d(116): Error: too many initializers for `Struct`
fail_compilation/test20998.d(116): Error: too many initializers for `Struct` with 1 field
immutable Struct iStruct = {1, &ch};
^
fail_compilation/test20998.d(120): called from here: `test2()`