diff --git a/compiler/include/dmd/aggregate.h b/compiler/include/dmd/aggregate.h index 47db3de44230..dea66fc50812 100644 --- a/compiler/include/dmd/aggregate.h +++ b/compiler/include/dmd/aggregate.h @@ -200,6 +200,15 @@ class UnionDeclaration final : public StructDeclaration void accept(Visitor *v) override { v->visit(this); } }; +class EnumUnionDeclaration final : public StructDeclaration +{ +public: + EnumUnionDeclaration *syntaxCopy(Dsymbol *s) override; + const char *kind() const override; + + void accept(Visitor *v) override { v->visit(this); } +}; + struct BaseClass { Type *type; // (before semantic processing) diff --git a/compiler/include/dmd/declaration.h b/compiler/include/dmd/declaration.h index 964e10ebcbfa..ff95ec67fddb 100644 --- a/compiler/include/dmd/declaration.h +++ b/compiler/include/dmd/declaration.h @@ -193,6 +193,15 @@ class TupleDeclaration final : public Declaration void accept(Visitor *v) override { v->visit(this); } }; +class EnumUnionCaseDeclaration final : public Declaration +{ +public: + EnumUnionCaseDeclaration *syntaxCopy(Dsymbol *) override; + const char *kind() const override; + + void accept(Visitor *v) override { v->visit(this); } +}; + /**************************************************************/ class AliasDeclaration final : public Declaration diff --git a/compiler/include/dmd/dsymbol.h b/compiler/include/dmd/dsymbol.h index da7105339322..99df03fb87a4 100644 --- a/compiler/include/dmd/dsymbol.h +++ b/compiler/include/dmd/dsymbol.h @@ -31,9 +31,9 @@ class AggregateDeclaration; class EnumDeclaration; class ClassDeclaration; class StructDeclaration; -class InterfaceDeclaration; -class StructDeclaration; class UnionDeclaration; +class EnumUnionDeclaration; +class EnumUnionCaseDeclaration; class FuncDeclaration; class FuncAliasDeclaration; class OverDeclaration; @@ -290,6 +290,7 @@ class Dsymbol : public ASTNode ClassDeclaration *isClassDeclaration(); StructDeclaration *isStructDeclaration(); UnionDeclaration *isUnionDeclaration(); + EnumUnionDeclaration *isEnumUnionDeclaration(); InterfaceDeclaration *isInterfaceDeclaration(); ScopeDsymbol *isScopeDsymbol(); ForwardingScopeDsymbol *isForwardingScopeDsymbol(); @@ -307,6 +308,7 @@ class Dsymbol : public ASTNode StaticAssert *isStaticAssert(); StaticIfDeclaration *isStaticIfDeclaration(); CAsmDeclaration *isCAsmDeclaration(); + EnumUnionCaseDeclaration *isEnumUnionCaseDeclaration(); void accept(Visitor *v) override { v->visit(this); } }; diff --git a/compiler/include/dmd/expression.h b/compiler/include/dmd/expression.h index 6d135576e86b..031bf967d5ef 100644 --- a/compiler/include/dmd/expression.h +++ b/compiler/include/dmd/expression.h @@ -40,6 +40,7 @@ class OverloadSet; class StringExp; class InterpExp; class LoweredAssignExp; +class SwitchExp; class StaticForeach; #ifdef IN_GCC typedef union tree_node Symbol; @@ -215,6 +216,7 @@ class Expression : public ASTNode EqualExp* isEqualExp(); IdentityExp* isIdentityExp(); CondExp* isCondExp(); + SwitchExp* isSwitchExp(); GenericExp* isGenericExp(); DefaultInitExp* isDefaultInitExp(); ClassReferenceExp* isClassReferenceExp(); @@ -1243,6 +1245,19 @@ class CondExp final : public BinExp void accept(Visitor *v) override { v->visit(this); } }; +struct CaseExpArm; + +class SwitchExp final : public Expression +{ +public: + Expression *condition; + DArray arms; + bool hasDefault; + + SwitchExp *syntaxCopy() override; + void accept(Visitor *v) override { v->visit(this); } +}; + class GenericExp final : Expression { Expression *cntlExp; diff --git a/compiler/include/dmd/tokens.h b/compiler/include/dmd/tokens.h index adc050df2cb4..a124e4a5453f 100644 --- a/compiler/include/dmd/tokens.h +++ b/compiler/include/dmd/tokens.h @@ -170,6 +170,7 @@ enum class TOK : unsigned char interface_, union_, enum_, + enumUnion, import_, alias_, override_, @@ -384,6 +385,7 @@ enum class EXP : unsigned char dot, comma, question, + switchExpression, andAnd, orOr, prePlusPlus, diff --git a/compiler/include/dmd/visitor.h b/compiler/include/dmd/visitor.h index 674ade585c20..191e17193bca 100644 --- a/compiler/include/dmd/visitor.h +++ b/compiler/include/dmd/visitor.h @@ -132,6 +132,7 @@ class CAsmDeclaration; class AggregateDeclaration; class StructDeclaration; class UnionDeclaration; +class EnumUnionDeclaration; class ClassDeclaration; class InterfaceDeclaration; @@ -140,6 +141,7 @@ class TupleDeclaration; class AliasDeclaration; class OverDeclaration; class VarDeclaration; +class EnumUnionCaseDeclaration; class SymbolDeclaration; class ThisDeclaration; class BitFieldDeclaration; @@ -298,6 +300,7 @@ class ClassReferenceExp; class VoidInitExp; class ThrownExceptionExp; class GenericExp; +class SwitchExp; class TemplateParameter; class TemplateTypeParameter; @@ -351,6 +354,7 @@ class ParseTimeVisitor virtual void visit(FuncDeclaration *s) { visit((Declaration *)s); } virtual void visit(AliasDeclaration *s) { visit((Declaration *)s); } virtual void visit(TupleDeclaration *s) { visit((Declaration *)s); } + virtual void visit(EnumUnionCaseDeclaration *s) { visit((Declaration *)s); } // FuncDeclarations virtual void visit(FuncLiteralDeclaration *s) { visit((FuncDeclaration *)s); } @@ -387,6 +391,7 @@ class ParseTimeVisitor virtual void visit(Module *s) { visit((Package *)s); } virtual void visit(StructDeclaration *s) { visit((AggregateDeclaration *)s); } virtual void visit(UnionDeclaration *s) { visit((StructDeclaration *)s); } + virtual void visit(EnumUnionDeclaration *s) { visit((StructDeclaration *)s); } virtual void visit(ClassDeclaration *s) { visit((AggregateDeclaration *)s); } virtual void visit(InterfaceDeclaration *s) { visit((ClassDeclaration *)s); } virtual void visit(TemplateMixin *s) { visit((TemplateInstance *)s); } @@ -499,6 +504,7 @@ class ParseTimeVisitor virtual void visit(TupleExp *e) { visit((Expression *)e); } virtual void visit(ThisExp *e) { visit((Expression *)e); } virtual void visit(GenericExp *e) { visit((Expression *)e); } + virtual void visit(SwitchExp *e) { visit((Expression *)e); } // Miscellaneous virtual void visit(VarExp *e) { visit((SymbolExp *)e); } diff --git a/compiler/src/build.d b/compiler/src/build.d index 921b737dd4d0..d063068aef01 100755 --- a/compiler/src/build.d +++ b/compiler/src/build.d @@ -1392,7 +1392,7 @@ auto sourceFiles() access.d aggregate.d aliasthis.d argtypes_x86.d argtypes_sysv_x64.d argtypes_aarch64.d arrayop.d arraytypes.d astenums.d ast_node.d astcodegen.d asttypename.d attrib.d attribsem.d blockexit.d builtin.d canthrow.d chkformat.d cli.d clone.d compiler.d cond.d constfold.d cpreprocess.d ctfeexpr.d - ctorflow.d dcast.d dclass.d declaration.d delegatize.d denum.d deps.d dimport.d + ctorflow.d dcast.d dclass.d declaration.d decisiontree.d delegatize.d denum.d deps.d dimport.d dinterpret.d dmacro.d dmodule.d doc.d dscope.d dstruct.d dsymbol.d dsymbolsem.d dtemplate.d dtoh.d dversion.d enumsem.d escape.d expression.d expressionsem.d func.d funcsem.d hdrgen.d impcnvtab.d imphint.d importc.d init.d initsem.d inline.d inlinecost.d intrange.d json.d lambdacomp.d diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index c40db08b8663..8e4d406058a1 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -62,6 +62,7 @@ struct ASTBase alias Visitor = ParseTimeVisitor!ASTBase; + extern (C++) abstract class ASTNode : RootObject { abstract void accept(Visitor v); @@ -228,6 +229,16 @@ struct ASTBase return null; } + inout(EnumUnionDeclaration) isEnumUnionDeclaration() inout + { + return null; + } + + inout(EnumUnionCaseDeclaration) isEnumUnionCaseDeclaration() inout + { + return null; + } + inout(ClassDeclaration) isClassDeclaration() inout { return null; @@ -893,6 +904,40 @@ struct ASTBase } } + struct EnumUnionVariant + { + Loc loc; + Identifier ident; + bool isTypeAlias; + Expressions* udas; + Type[] payload; + Identifier[] payloadNames; + Dsymbols* members; + TraitsExp variantSplice; + } + + extern (C++) final class EnumUnionCaseDeclaration : Declaration + { + EnumUnionVariant variant; + + extern (D) this(Loc loc, EnumUnionVariant variant) + { + super(null); + this.loc = loc; + this.variant = variant; + } + + override void accept(Visitor v) + { + v.visit(this); + } + + override inout(EnumUnionCaseDeclaration) isEnumUnionCaseDeclaration() inout + { + return this; + } + } + extern (C++) abstract class AggregateDeclaration : ScopeDsymbol { Visibility visibility; @@ -1491,6 +1536,28 @@ struct ASTBase } } + extern (C++) final class EnumUnionDeclaration : StructDeclaration + { + EnumUnionVariant[] variants; + VarDeclaration tagVar; + UnionDeclaration payloadUnion; + + extern (D) this(Loc loc, Identifier id) + { + super(loc, id, false); + } + + override void accept(Visitor v) + { + v.visit(this); + } + + override inout(EnumUnionDeclaration) isEnumUnionDeclaration() inout + { + return this; + } + } + extern (C++) class ClassDeclaration : AggregateDeclaration { extern (C++) __gshared @@ -4604,6 +4671,7 @@ struct ASTBase inout(DotIdExp) isDotIdExp() { return op == EXP.dotIdentifier ? cast(typeof(return))this : null; } inout(DotTemplateInstanceExp) isDotTemplateInstanceExp() { return op == EXP.dotTemplateInstance ? cast(typeof(return))this : null; } inout(CallExp) isCallExp() { return op == EXP.call ? cast(typeof(return))this : null; } + inout(SwitchExp) isSwitchExp() { return op == EXP.switchExpression ? cast(typeof(return))this : null; } inout(AddrExp) isAddrExp() { return op == EXP.address ? cast(typeof(return))this : null; } inout(PtrExp) isPtrExp() { return op == EXP.star ? cast(typeof(return))this : null; } inout(NegExp) isNegExp() { return op == EXP.negate ? cast(typeof(return))this : null; } @@ -5230,6 +5298,7 @@ struct ASTBase { Dsymbol s; bool hasOverloads; + bool preserveSymbol; extern (D) this(Loc loc, Dsymbol s, bool hasOverloads = true) { @@ -6022,6 +6091,85 @@ struct ASTBase } } + struct CaseExpArm + { + Loc loc; + Expression pattern; + Type typePattern; + Identifier typeBinding; + Identifier[] recordBindings; + bool hasRestPattern; + Identifier[] recordPatternNames; + Expression[] recordPatterns; + Identifier restBinding; + Expression guard; + bool isDefault; + Expression action; + + StaticForeach sfe; + CaseExpArm[] nestedArms; + Condition staticIfCond; + CaseExpArm[] elseArms; + + CaseExpArm syntaxCopy() + { + CaseExpArm copy = this; + copy.pattern = pattern ? pattern.syntaxCopy() : null; + copy.typePattern = typePattern ? typePattern.syntaxCopy() : null; + copy.recordBindings = recordBindings.dup; + copy.recordPatternNames = recordPatternNames.dup; + if (recordPatterns) + { + copy.recordPatterns = new Expression[](recordPatterns.length); + foreach (j, pat; recordPatterns) + copy.recordPatterns[j] = pat ? pat.syntaxCopy() : null; + } + copy.guard = guard ? guard.syntaxCopy() : null; + copy.action = action ? action.syntaxCopy() : null; + if (nestedArms) + { + copy.nestedArms = new CaseExpArm[](nestedArms.length); + foreach (j, ref na; nestedArms) + copy.nestedArms[j] = na.syntaxCopy(); + } + if (elseArms) + { + copy.elseArms = new CaseExpArm[](elseArms.length); + foreach (j, ref ea; elseArms) + copy.elseArms[j] = ea.syntaxCopy(); + } + return copy; + } + } + + extern (C++) final class SwitchExp : Expression + { + Expression condition; + CaseExpArm[] arms; + bool hasDefault; + + final extern (D) this(Loc loc, Expression condition, CaseExpArm[] arms, bool hasDefault) + { + super(loc, EXP.switchExpression, __traits(classInstanceSize, SwitchExp)); + this.condition = condition; + this.arms = arms; + this.hasDefault = hasDefault; + } + + override SwitchExp syntaxCopy() + { + auto copiedArms = new CaseExpArm[](arms.length); + foreach (i, arm; arms) + copiedArms[i] = arm.syntaxCopy(); + return new SwitchExp(loc, condition.syntaxCopy(), copiedArms, hasDefault); + } + + override void accept(Visitor v) + { + v.visit(this); + } + } + extern (C++) final class AssignExp : BinExp { extern (D) this(Loc loc, Expression e1, Expression e2) diff --git a/compiler/src/dmd/astcodegen.d b/compiler/src/dmd/astcodegen.d index ae0c3629178b..d8825974c9a5 100644 --- a/compiler/src/dmd/astcodegen.d +++ b/compiler/src/dmd/astcodegen.d @@ -42,6 +42,7 @@ struct ASTCodegen alias Ensure = dmd.func.Ensure; // workaround for bug in older DMD frontends alias ErrorExp = dmd.expression.ErrorExp; alias ArgumentLabel = dmd.expression.ArgumentLabel; + alias CaseExpArm = dmd.expression.CaseExpArm; alias MODFlags = dmd.mtype.MODFlags; alias Type = dmd.mtype.Type; diff --git a/compiler/src/dmd/backend/x86/cod2.d b/compiler/src/dmd/backend/x86/cod2.d index 494cd93c6283..be4e649ebeff 100644 --- a/compiler/src/dmd/backend/x86/cod2.d +++ b/compiler/src/dmd/backend/x86/cod2.d @@ -2543,7 +2543,11 @@ void cdcond(ref CGstate cg, ref CodeBuilder cdb,elem* e,ref regm_t pretregs) fixresult(cg,cdb2,e22,retregs,pretregs); } else + { + if (pretregs == 0) + retregs = 0; codelem(cg,cdb2,e22,retregs,false); // use same regs as E1 + } pretregs = retregs | psw; andregcon(regconold); andregcon(regconsave); diff --git a/compiler/src/dmd/ctorflow.d b/compiler/src/dmd/ctorflow.d index 4207c681e2c4..64362781625a 100644 --- a/compiler/src/dmd/ctorflow.d +++ b/compiler/src/dmd/ctorflow.d @@ -40,6 +40,7 @@ struct FieldInit struct CtorFlow { CSX callSuper; /// state of calling other constructors + bool thisInitialized; /// an enum-union constructor assigned a complete value to `this` FieldInit[] fieldinit; /// state of field initializations @@ -63,7 +64,7 @@ struct CtorFlow */ CtorFlow clone() { - return CtorFlow(callSuper, fieldinit.arraydup); + return CtorFlow(callSuper, thisInitialized, fieldinit.arraydup); } /********************************** @@ -86,6 +87,7 @@ struct CtorFlow void OR(const ref CtorFlow ctorflow) pure nothrow @safe { callSuper |= ctorflow.callSuper; + thisInitialized |= ctorflow.thisInitialized; if (fieldinit.length && ctorflow.fieldinit.length) { assert(fieldinit.length == ctorflow.fieldinit.length); @@ -100,6 +102,24 @@ struct CtorFlow } } +/**************************************** + * Merge whether `this` has been initialized along all continuing paths. + */ +void mergeThisInitialized(ref bool a, const bool b, const CSX aCSX, const CSX bCSX) + pure nothrow @safe +{ + const aExits = aCSX & (CSX.return_ | CSX.halt); + const bExits = bCSX & (CSX.return_ | CSX.halt); + if (bExits && !aExits) + return; + if (aExits && !bExits) + { + a = b; + return; + } + a &= b; +} + /**************************************** * Merge `b` flow analysis results into `a`. diff --git a/compiler/src/dmd/dcast.d b/compiler/src/dmd/dcast.d index 8c7ddd1beeea..8711911ba2b7 100644 --- a/compiler/src/dmd/dcast.d +++ b/compiler/src/dmd/dcast.d @@ -32,6 +32,7 @@ import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.hdrgen; +import dmd.identifier; import dmd.location; import dmd.impcnvtab; import dmd.importc; @@ -94,6 +95,46 @@ IntRange intRangeFromType(Type type, bool isUnsigned) return IntRange(lower, upper); } +Expression initializeEnumUnionVariant(Loc loc, VarDeclaration temporary, + EnumUnionDeclaration enumUnion, size_t variantIndex, Expressions* values) +{ + auto variant = enumUnion.variants[variantIndex]; + Expression result; + + if (variant.payloadType && variant.payloadType.fields.length) + { + Expression payload; + if (variant.declaration && variant.declaration.isStructDeclaration()) + payload = (*values)[0]; + else + payload = new StructLiteralExp(loc, variant.payloadType, values, + variant.payloadType.type); + auto payloadAccess = new DotVarExp(loc, new VarExp(loc, temporary), + variant.payloadVar); + result = new ConstructExp(loc, payloadAccess, payload); + } + + auto tagExp = new DotVarExp(loc, new VarExp(loc, temporary), enumUnion.tagVar); + result = Expression.combine(result, new AssignExp(loc, tagExp, + new IntegerExp(loc, variantIndex, Type.tuns8))); + return result; +} + +Expression constructEnumUnionVariant(Expression value, Scope* sc, Type type, + EnumUnionDeclaration enumUnion, size_t variantIndex) +{ + auto temporary = new VarDeclaration(value.loc, type, Identifier.generateId("__enumConv"), + new VoidInitializer(value.loc)); + temporary.storage_class |= STC.temp | STC.nodtor; + Expression result = new DeclarationExp(value.loc, temporary).expressionSemantic(sc); + auto values = new Expressions(value); + auto initialize = initializeEnumUnionVariant(value.loc, temporary, enumUnion, + variantIndex, values).expressionSemantic(sc); + result = Expression.combine(result, initialize); + auto temporaryValue = new VarExp(value.loc, temporary).expressionSemantic(sc); + return Expression.combine(result, temporaryValue); +} + /** * Attempt to implicitly cast the expression into type `t`. * @@ -113,6 +154,60 @@ Expression implicitCastTo(Expression e, Scope* sc, Type t) { auto eSink = global.errorSink; + if (auto ts = t.toBasetype().isTypeStruct()) + { + if (auto eu = ts.sym.isEnumUnionDeclaration()) + { + size_t matchIndex = size_t.max; + size_t ambiguousIndex = size_t.max; + MATCH bestMatch = MATCH.nomatch; + foreach (i, variant; eu.variants) + { + auto recordDeclaration = variant.declaration + ? variant.declaration.isStructDeclaration() : null; + auto payloadType = recordDeclaration ? recordDeclaration.type + : variant.payloadType && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type : variant.payload.length ? variant.payload[0] : null; + if ((!variant.ident || recordDeclaration) && + (recordDeclaration || variant.payload.length == 1) && payloadType) + { + auto match = e.implicitConvTo(payloadType); + if (match < MATCH.convert) + continue; + if (auto fe = e.isFuncExp()) + { + if (fe.fd && fe.fd.tok == TOK.reserved && payloadType.isFunction_Delegate_PtrToFunction()) + match = MATCH.convert; + } + if (match > bestMatch) + { + bestMatch = match; + matchIndex = i; + ambiguousIndex = size_t.max; + } + else if (match == bestMatch) + ambiguousIndex = i; + } + } + if (ambiguousIndex != size_t.max) + { + auto ambiguousVariant = eu.variants[ambiguousIndex]; + auto ambiguousType = ambiguousVariant.payloadType && ambiguousVariant.payloadType.fields.length + ? ambiguousVariant.payloadType.fields[0].type + : ambiguousVariant.payload.length ? ambiguousVariant.payload[0] : null; + eSink.error(e.loc, "`%s` is ambiguous between variants `%s` and `%s` of enum union `%s`", + e.toErrMsg(), eu.variants[matchIndex].payloadType && eu.variants[matchIndex].payloadType.fields.length + ? eu.variants[matchIndex].payloadType.fields[0].type.toErrMsg() + : "", + ambiguousType ? ambiguousType.toErrMsg() : "", + eu.toPrettyChars()); + return ErrorExp.get(); + } + if (matchIndex != size_t.max) + return constructEnumUnionVariant(e, sc, t, eu, matchIndex); + } + } + Expression visit(Expression e) { //printf("Expression.implicitCastTo(%s of type %s) => %s\n", e.toChars(), e.type.toChars(), t.toChars()); @@ -1561,6 +1656,28 @@ MATCH implicitConvTo(Expression e, Type t) */ MATCH implicitConvTo(Type from, Type to) { + if (auto ts = to.toBasetype().isTypeStruct()) + { + if (auto eu = ts.sym.isEnumUnionDeclaration()) + { + foreach (variant; eu.variants) + { + auto recordDeclaration = variant.declaration + ? variant.declaration.isStructDeclaration() : null; + auto payloadType = recordDeclaration ? recordDeclaration.type + : variant.payloadType && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type : variant.payload.length ? variant.payload[0] : null; + const requiredMatch = payloadType && payloadType.isFunction_Delegate_PtrToFunction() && + from.isFunction_Delegate_PtrToFunction() + ? MATCH.convert : MATCH.exact; + if ((!variant.ident || recordDeclaration) && + (recordDeclaration || variant.payload.length == 1) && payloadType && + from.implicitConvTo(payloadType) >= requiredMatch) + return MATCH.convert; + } + } + } + MATCH visitType(Type from) { //printf("Type::implicitConvTo(this=%p, to=%p)\n", this, to); diff --git a/compiler/src/dmd/decisiontree.d b/compiler/src/dmd/decisiontree.d new file mode 100644 index 000000000000..528df421a28a --- /dev/null +++ b/compiler/src/dmd/decisiontree.d @@ -0,0 +1,317 @@ +/** Pattern usefulness and exhaustiveness checking for switch expressions. + * + * The matrix owns only its initial rows. Recursive specialization passes row + * indices and a column offset, so it never clones AST expressions or patterns. + */ +module dmd.decisiontree; + +import dmd.dstruct; +import dmd.declaration; +import dmd.astenums; +import dmd.errorsink; +import dmd.expression; +import dmd.location; +import dmd.mtype; +import dmd.typesem; +import dmd.common.outbuffer; + +private enum PatternKind : ubyte +{ + wildcard, + constructor, + literal, +} + +private struct Pattern +{ + PatternKind kind; + size_t value; + Loc loc; + bool isBool; + Expression expr; +} + +private struct MatrixRow +{ + Pattern[] columns; + size_t armIndex; +} + +private struct PatternMatrix +{ + MatrixRow[] rows; + size_t numColumns; +} + +private bool expressionsEqual(const(Expression) e1, const(Expression) e2) +{ + if (e1 is e2) + return true; + if (!e1 || !e2) + return false; + if (e1.op != e2.op) + return false; + if (auto ie1 = (cast()e1).isIntegerExp()) + if (auto ie2 = (cast()e2).isIntegerExp()) + return ie1.getInteger() == ie2.getInteger(); + if (auto re1 = (cast()e1).isRealExp()) + if (auto re2 = (cast()e2).isRealExp()) + return re1.value == re2.value; + if (auto se1 = (cast()e1).isStringExp()) + if (auto se2 = (cast()e2).isStringExp()) + return se1.peekData() == se2.peekData(); + if ((cast()e1).isNullExp() && (cast()e2).isNullExp()) + return true; + return e1.toString() == e2.toString(); +} + +private bool matches(ref const Pattern row, ref const Pattern value) +{ + if (row.kind == PatternKind.wildcard || value.kind == PatternKind.wildcard) + return true; + if (row.kind != value.kind) + return false; + if (row.expr || value.expr) + { + if (!row.expr || !value.expr) + return false; + return expressionsEqual(row.expr, value.expr); + } + return row.value == value.value; +} + +private size_t constructorCount(EnumUnionDeclaration eu, size_t column, size_t[] fieldOffsets) +{ + if (column == 0) + return eu.variants.length; + return 0; +} + +/* + * Maranget's usefulness recurrence, represented as a borrowed view over the + * original matrix. The root column is a finite enum-union constructor space; + * bool fields are also finite. Integer fields use the literal/default split, + * which represents all values not named in the matrix with one branch. + */ +private bool isUseful(ref const PatternMatrix matrix, size_t[] rowIndices, + Pattern[] vector, size_t column, EnumUnionDeclaration eu, size_t[] fieldOffsets) +{ + if (!rowIndices.length) + return true; + if (column == matrix.numColumns) + return false; + + const candidate = vector[column]; + const constructors = constructorCount(eu, column, fieldOffsets); + if (candidate.kind == PatternKind.constructor || candidate.kind == PatternKind.literal) + { + size_t[] specialized; + foreach (rowIndex; rowIndices) + if (matches(matrix.rows[rowIndex].columns[column], candidate)) + specialized ~= rowIndex; + return isUseful(matrix, specialized, vector, column + 1, eu, fieldOffsets); + } + + if (constructors) + { + // <= 64 enum-union variants are tracked in one mask. The language + // currently caps them at 256, so the larger case falls back to this + // same bounded constructor iteration. + ulong seen; + foreach (rowIndex; rowIndices) + { + const pattern = matrix.rows[rowIndex].columns[column]; + if (pattern.kind == PatternKind.constructor && pattern.value < 64) + seen |= 1UL << pattern.value; + } + foreach (constructor; 0 .. constructors) + { + Pattern specialized = Pattern(PatternKind.constructor, constructor, candidate.loc); + size_t[] rows; + foreach (rowIndex; rowIndices) + if (matches(matrix.rows[rowIndex].columns[column], specialized)) + rows ~= rowIndex; + auto trial = vector.dup; + trial[column] = specialized; + if (isUseful(matrix, rows, trial, column + 1, eu, fieldOffsets)) + return true; + } + return false; + } + + // A bool constructor space is finite even though it is represented by an + // integer literal in the frontend AST. + bool isBool; + foreach (rowIndex; rowIndices) + { + const pattern = matrix.rows[rowIndex].columns[column]; + if (pattern.kind == PatternKind.literal && pattern.isBool) + isBool = true; + } + if (isBool) + { + foreach (value; 0 .. 2) + { + Pattern literal = Pattern(PatternKind.literal, value, candidate.loc, true); + size_t[] rows; + foreach (rowIndex; rowIndices) + if (matches(matrix.rows[rowIndex].columns[column], literal)) + rows ~= rowIndex; + auto trial = vector.dup; + trial[column] = literal; + if (isUseful(matrix, rows, trial, column + 1, eu, fieldOffsets)) + return true; + } + return false; + } + + // Literal partitions: check each named point once, then the single + // default interval that contains every other scalar value. + foreach (rowIndex; rowIndices) + { + const literal = matrix.rows[rowIndex].columns[column]; + if (literal.kind != PatternKind.literal) + continue; + bool seen; + foreach (previous; rowIndices) + { + if (previous == rowIndex) + break; + const other = matrix.rows[previous].columns[column]; + if (other.kind == PatternKind.literal && matches(other, literal)) + { + seen = true; + break; + } + } + if (seen) + continue; + size_t[] rows; + foreach (index; rowIndices) + if (matches(matrix.rows[index].columns[column], literal)) + rows ~= index; + auto trial = vector.dup; + trial[column] = cast()literal; + if (isUseful(matrix, rows, trial, column + 1, eu, fieldOffsets)) + return true; + } + size_t[] defaults; + foreach (rowIndex; rowIndices) + if (matrix.rows[rowIndex].columns[column].kind == PatternKind.wildcard) + defaults ~= rowIndex; + return isUseful(matrix, defaults, vector, column + 1, eu, fieldOffsets); +} + +private MatrixRow makeRow(ref CaseExpArm arm, EnumUnionDeclaration eu, size_t[] fieldOffsets, + size_t totalColumns, size_t armIndex) +{ + MatrixRow row; + row.columns.length = totalColumns; + foreach (ref pattern; row.columns) + pattern = Pattern(PatternKind.wildcard, 0, arm.loc); + row.armIndex = armIndex; + if (arm.isDefault) + return row; + row.columns[0] = Pattern(PatternKind.constructor, arm.variantIndex, arm.loc); + return row; +} + +private void missingWitness(ref OutBuffer witness, ref const PatternMatrix matrix, size_t[] allRows, + EnumUnionDeclaration eu, size_t[] fieldOffsets, size_t totalColumns, Loc loc) +{ + foreach (variantIndex, variant; eu.variants) + { + Pattern[] candidate = new Pattern[](totalColumns); + foreach (ref pattern; candidate) + pattern = Pattern(PatternKind.wildcard, 0, loc); + candidate[0] = Pattern(PatternKind.constructor, variantIndex, loc); + if (!isUseful(matrix, allRows, candidate, 0, eu, fieldOffsets)) + continue; + if (variant.ident) + witness.writestring(variant.ident.toString()); + else if (variant.payloadType && variant.payloadType.fields.length == 1) + witness.writestring(variant.payloadType.fields[0].type.toChars()); + else + witness.writestring("_"); + if (variant.payloadType && variant.payloadType.fields.length) + { + if (!variant.ident) + return; + witness.writeByte('('); + foreach (index; 0 .. variant.payloadType.fields.length) + { + if (index) + witness.writestring(", "); + witness.writeByte('_'); + } + witness.writeByte(')'); + } + return; + } + witness.writestring("_"); +} + +/** Check source-order usefulness and whether unguarded arms cover all cases. */ +public bool checkExhaustivenessAndRedundancy(SwitchExp exp, EnumUnionDeclaration eu, ErrorSink eSink) +{ + size_t[] fieldOffsets; + size_t totalColumns = 1; + foreach (variant; eu.variants) + { + fieldOffsets ~= totalColumns; + totalColumns += variant.payloadType ? variant.payloadType.fields.length : 0; + } + + PatternMatrix matrix = PatternMatrix(null, totalColumns); + bool hasDefault; + foreach (armIndex, ref arm; exp.arms) + { + if (arm.isDefault) + { + Pattern[] wildcard = new Pattern[](totalColumns); + foreach (ref pattern; wildcard) + pattern = Pattern(PatternKind.wildcard, 0, arm.loc); + size_t[] allRows; + foreach (index; 0 .. matrix.rows.length) + allRows ~= index; + if (!isUseful(matrix, allRows, wildcard, 0, eu, fieldOffsets)) + { + eSink.error(arm.loc, "redundant match arm; pattern is unreachable"); + return false; + } + matrix.rows ~= MatrixRow(wildcard, armIndex); + hasDefault = true; + continue; + } + size_t[] allRows; + foreach (index; 0 .. matrix.rows.length) + allRows ~= index; + auto row = makeRow(arm, eu, fieldOffsets, totalColumns, armIndex); + if (!isUseful(matrix, allRows, row.columns, 0, eu, fieldOffsets)) + { + eSink.error(arm.loc, "redundant match arm; pattern is unreachable"); + return false; + } + if (!arm.guard) + matrix.rows ~= row; + } + + if (hasDefault) + return true; + + Pattern[] wildcard = new Pattern[](totalColumns); + foreach (ref pattern; wildcard) + pattern = Pattern(PatternKind.wildcard, 0, exp.loc); + size_t[] allRows; + foreach (index; 0 .. matrix.rows.length) + allRows ~= index; + if (isUseful(matrix, allRows, wildcard, 0, eu, fieldOffsets)) + { + OutBuffer witness; + missingWitness(witness, matrix, allRows, eu, fieldOffsets, totalColumns, exp.loc); + eSink.error(exp.loc, "switch expression is not exhaustive; missing pattern `%s`", + witness.peekChars()); + return false; + } + return true; +} diff --git a/compiler/src/dmd/dfa/fast/expression.d b/compiler/src/dmd/dfa/fast/expression.d index 5279a7fcccf9..a3dff6f59758 100644 --- a/compiler/src/dmd/dfa/fast/expression.d +++ b/compiler/src/dmd/dfa/fast/expression.d @@ -1480,6 +1480,13 @@ struct ExpressionWalker return inProgress; } + case EXP.switchExpression: + auto switchExp = expr.isSwitchExp; + this.walk(switchExp.condition); + foreach (arm; switchExp.arms) + this.walk(arm.action); + return DFALatticeRef.init; + case EXP.question: { auto qe = expr.isCondExp; diff --git a/compiler/src/dmd/dinterpret.d b/compiler/src/dmd/dinterpret.d index df9d9f2bb3fd..502f58555d6b 100644 --- a/compiler/src/dmd/dinterpret.d +++ b/compiler/src/dmd/dinterpret.d @@ -6175,6 +6175,35 @@ public: return; } + if (auto eu = se.sd.isEnumUnionDeclaration()) + { + size_t variantIndex = size_t.max; + foreach (index, variant; eu.variants) + { + if (v is variant.payloadVar) + { + variantIndex = index; + break; + } + } + if (variantIndex != size_t.max) + { + const tagIndex = findFieldIndexByName(se.sd, eu.tagVar); + if (tagIndex != -1) + { + if (auto tag = (*se.elements)[tagIndex].isIntegerExp()) + { + if (tag.toUInteger() != variantIndex) + { + eSink.error(e.loc, "reinterpretation through overlapped field `%s` is not allowed in CTFE", v.toChars()); + result = CTFEExp.cantexp; + return; + } + } + } + } + } + // https://issues.dlang.org/show_bug.cgi?id=19897 // https://issues.dlang.org/show_bug.cgi?id=20710 // Zero-elements fields don't have an initializer. See: scrubArray function diff --git a/compiler/src/dmd/dscope.d b/compiler/src/dmd/dscope.d index dd2724a845df..e924fc6cf222 100644 --- a/compiler/src/dmd/dscope.d +++ b/compiler/src/dmd/dscope.d @@ -48,6 +48,7 @@ enum Contract : ubyte private extern (D) struct FlagBitFields { bool ctor; /// constructor type + bool allowUninitializedThis; /// analyzing the left side of `this = value` in an enum-union constructor bool noAccessCheck; /// don't do access checks bool condition; /// inside static if/assert condition bool debug_; /// inside debug conditional diff --git a/compiler/src/dmd/dstruct.d b/compiler/src/dmd/dstruct.d index ea093b929a1f..bb658db755da 100644 --- a/compiler/src/dmd/dstruct.d +++ b/compiler/src/dmd/dstruct.d @@ -18,13 +18,17 @@ import core.stdc.stdio; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; +import dmd.declaration; +import dmd.denum; import dmd.dmodule; import dmd.dsymbol; +import dmd.expression; import dmd.func; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; +import dmd.rootobject; import dmd.visitor; enum StructFlags : int @@ -33,6 +37,65 @@ enum StructFlags : int hasPointers = 0x1, // NB: should use noPointers as in ClassFlags } +struct EnumUnionVariant +{ + Loc loc; + Identifier ident; + bool isTypeAlias; + bool generated; + Dsymbol declaration; + EnumUnionCaseDeclaration descriptor; + Expressions* udas; + Type[] payload; + Identifier[] payloadNames; + Dsymbols* members; + TraitsExp variantSplice; + StructDeclaration payloadType; + VarDeclaration payloadVar; +} + +package EnumUnionVariant syntaxCopyEnumUnionVariant(ref EnumUnionVariant variant) +{ + EnumUnionVariant copy; + copy.loc = variant.loc; + copy.ident = variant.ident; + copy.isTypeAlias = variant.isTypeAlias; + copy.udas = Expression.arraySyntaxCopy(variant.udas); + copy.payload.reserve(variant.payload.length); + foreach (payload; variant.payload) + copy.payload ~= payload.syntaxCopy(); + copy.payloadNames = variant.payloadNames.dup; + copy.members = Dsymbol.arraySyntaxCopy(variant.members); + copy.variantSplice = variant.variantSplice ? variant.variantSplice.syntaxCopy() : null; + return copy; +} + +extern (C++) final class EnumUnionCaseDeclaration : Declaration +{ + EnumUnionVariant variant; + + extern (D) this(Loc loc, EnumUnionVariant variant) + { + super(DSYM.enumUnionCaseDeclaration, loc, null); + this.variant = variant; + } + + override EnumUnionCaseDeclaration syntaxCopy(Dsymbol s) + { + return new EnumUnionCaseDeclaration(loc, syntaxCopyEnumUnionVariant(variant)); + } + + override const(char)* kind() const + { + return "enum union case"; + } + + override void accept(Visitor v) + { + v.visit(this); + } +} + /*********************************************************** * All `struct` declarations are an instance of this. */ @@ -135,6 +198,59 @@ extern (C++) class StructDeclaration : AggregateDeclaration } +/*********************************************************** + * Tagged aggregate used by `enum union` declarations. + */ +extern (C++) final class EnumUnionDeclaration : StructDeclaration +{ + EnumUnionVariant[] variants; + bool enumUnionCasesExpanded; + bool enumUnionFactoriesSynthesized; + VarDeclaration tagVar; + UnionDeclaration payloadUnion; + + extern (D) this(Loc loc, Identifier id) + { + super(loc, id, false); + this.dsym = DSYM.enumUnionDeclaration; + } + + override EnumUnionDeclaration syntaxCopy(Dsymbol s) + { + auto eu = new EnumUnionDeclaration(loc, ident); + eu.variants.reserve(variants.length); + foreach (ref variant; variants) + eu.variants ~= syntaxCopyEnumUnionVariant(variant); + StructDeclaration.syntaxCopy(eu); + if (members && eu.members) + foreach (i, member; *members) + { + if (i >= eu.members.length) + break; + if (member is tagVar) + eu.tagVar = (*eu.members)[i].isVarDeclaration(); + if (auto anon = member.isAnonDeclaration()) + if (anon.isunion) + { + auto copy = (*eu.members)[i].isAnonDeclaration(); + eu.payloadUnion = new UnionDeclaration(payloadUnion.loc, payloadUnion.ident); + eu.payloadUnion.members = copy ? copy.decl : null; + } + } + return eu; + } + + override const(char)* kind() const + { + return "enum union"; + } + + override void accept(Visitor v) + { + v.visit(this); + } +} + /*********************************************************** * Unions are a variation on structs. */ diff --git a/compiler/src/dmd/dsymbol.d b/compiler/src/dmd/dsymbol.d index c3dea7dfbbc5..94449293d527 100644 --- a/compiler/src/dmd/dsymbol.d +++ b/compiler/src/dmd/dsymbol.d @@ -343,6 +343,8 @@ enum DSYM : ubyte classDeclaration, structDeclaration, unionDeclaration, + enumUnionDeclaration, + enumUnionCaseDeclaration, interfaceDeclaration, scopeDsymbol, forwardingScopeDsymbol, @@ -924,6 +926,7 @@ extern (C++) class Dsymbol : ASTNode inout(TemplateInstance) isTemplateInstance() inout { return (dsym == DSYM.templateInstance || dsym == DSYM.templateMixin) ? cast(inout(TemplateInstance)) cast(void*) this : null; } inout(TemplateMixin) isTemplateMixin() inout { return dsym == DSYM.templateMixin ? cast(inout(TemplateMixin)) cast(void*) this : null; } inout(ForwardingAttribDeclaration) isForwardingAttribDeclaration() inout { return dsym == DSYM.forwardingAttribDeclaration ? cast(inout(ForwardingAttribDeclaration)) cast(void*) this : null; } + inout(StaticForeachDeclaration) isStaticForeachDeclaration() inout { return dsym == DSYM.staticForeachDeclaration ? cast(inout(StaticForeachDeclaration)) cast(void*) this : null; } inout(Nspace) isNspace() inout { return dsym == DSYM.nspace ? cast(inout(Nspace)) cast(void*) this : null; } inout(Declaration) isDeclaration() inout { switch (dsym) @@ -970,6 +973,7 @@ extern (C++) class Dsymbol : ASTNode case DSYM.aggregateDeclaration: case DSYM.structDeclaration: case DSYM.unionDeclaration: + case DSYM.enumUnionDeclaration: case DSYM.classDeclaration: case DSYM.interfaceDeclaration: return cast(inout(AggregateDeclaration)) cast(void*) this; @@ -1027,8 +1031,9 @@ extern (C++) class Dsymbol : ASTNode inout(VersionSymbol) isVersionSymbol() inout { return dsym == DSYM.versionSymbol ? cast(inout(VersionSymbol)) cast(void*) this : null; } inout(DebugSymbol) isDebugSymbol() inout { return dsym == DSYM.debugSymbol ? cast(inout(DebugSymbol)) cast(void*) this : null; } inout(ClassDeclaration) isClassDeclaration() inout { return (dsym == DSYM.classDeclaration || dsym == DSYM.interfaceDeclaration) ? cast(inout(ClassDeclaration)) cast(void*) this : null; } - inout(StructDeclaration) isStructDeclaration() inout { return (dsym == DSYM.structDeclaration || dsym == DSYM.unionDeclaration) ? cast(inout(StructDeclaration)) cast(void*) this : null; } + inout(StructDeclaration) isStructDeclaration() inout { return (dsym == DSYM.structDeclaration || dsym == DSYM.unionDeclaration || dsym == DSYM.enumUnionDeclaration) ? cast(inout(StructDeclaration)) cast(void*) this : null; } inout(UnionDeclaration) isUnionDeclaration() inout { return dsym == DSYM.unionDeclaration ? cast(inout(UnionDeclaration)) cast(void*) this : null; } + inout(EnumUnionDeclaration) isEnumUnionDeclaration() inout { return dsym == DSYM.enumUnionDeclaration ? cast(inout(EnumUnionDeclaration)) cast(void*) this : null; } inout(InterfaceDeclaration) isInterfaceDeclaration() inout { return dsym == DSYM.interfaceDeclaration ? cast(inout(InterfaceDeclaration)) cast(void*) this : null; } inout(ScopeDsymbol) isScopeDsymbol() inout { switch (dsym) @@ -1044,6 +1049,7 @@ extern (C++) class Dsymbol : ASTNode case DSYM.aggregateDeclaration: case DSYM.structDeclaration: case DSYM.unionDeclaration: + case DSYM.enumUnionDeclaration: case DSYM.classDeclaration: case DSYM.interfaceDeclaration: case DSYM.withScopeSymbol: @@ -1092,6 +1098,9 @@ extern (C++) class Dsymbol : ASTNode inout(StaticAssert) isStaticAssert() inout { return dsym == DSYM.staticAssert ? cast(inout(StaticAssert)) cast(void*) this : null; } inout(StaticIfDeclaration) isStaticIfDeclaration() inout { return dsym == DSYM.staticIfDeclaration ? cast(inout(StaticIfDeclaration)) cast(void*) this : null; } inout(CAsmDeclaration) isCAsmDeclaration() inout { return dsym == DSYM.cAsmDeclaration ? cast(inout(CAsmDeclaration)) cast(void*) this : null; } + inout(PragmaDeclaration) isPragmaDeclaration() inout { return dsym == DSYM.pragmaDeclaration ? cast(inout(PragmaDeclaration)) cast(void*) this : null; } + inout(UserAttributeDeclaration) isUserAttributeDeclaration() inout { return dsym == DSYM.userAttributeDeclaration ? cast(inout(UserAttributeDeclaration)) cast(void*) this : null; } + inout(EnumUnionCaseDeclaration) isEnumUnionCaseDeclaration() inout { return dsym == DSYM.enumUnionCaseDeclaration ? cast(inout(EnumUnionCaseDeclaration)) cast(void*) this : null; } } /*********************************************************** diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 4b664dd12db2..a5af24040dc1 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -1030,6 +1030,8 @@ Type getType(Dsymbol _this) return aggregateDeclGetType(agd); else if (auto ed = _this.isEnumDeclaration()) return ed.type; + else if (auto variant = _this.isEnumUnionCaseDeclaration()) + return variant.type; // is this a type? return null; @@ -2091,6 +2093,931 @@ private void checkImportDeprecation(Module m, Loc loc, Scope* sc) eSink.deprecation(m.loc, "%s `%s` is deprecated", m.kind, m.toPrettyChars); } +private bool expandEnumUnionVariantSplice(ref EnumUnionVariant splice, Scope* sc, + out EnumUnionVariant expanded, Dsymbol forcedSourceSymbol = null) +{ + auto eSink = global.errorSink; + auto trait = splice.variantSplice; + const dim = trait.args ? trait.args.length : 0; + if (dim < 1 || dim > 2) + { + eSink.error(trait.loc, "expected 1 or 2 arguments for `%s` but had %d", + trait.ident.toErrMsg(), cast(int) dim); + return false; + } + + Identifier overrideName; + if (dim == 2) + { + auto renameExpression = isExpression((*trait.args)[1]); + auto rename = renameExpression + ? semanticString(sc, renameExpression, "variant name in `__traits(variantDeclarationOf)`") + : null; + if (!rename) + return false; + auto name = rename.toUTF8(sc).peekString(); + if (name.length) + { + if (!Identifier.isValidIdentifier(name)) + { + eSink.error(trait.loc, + "`%.*s` is not a valid identifier token for variant declaration in `__traits(variantDeclarationOf)`", + cast(int) name.length, name.ptr); + return false; + } + overrideName = Identifier.idPool(name); + const token = cast(TOK) overrideName.getValue(); + if (token != TOK.identifier && token < FirstCKeyword) + { + eSink.error(trait.loc, + "cannot use reserved keyword `%s` as a variant identifier in `__traits(variantDeclarationOf)`", + overrideName.toErrMsg()); + return false; + } + } + } + + auto sourceObject = (*trait.args)[0]; + Dsymbol sourceSymbol = forcedSourceSymbol; + if (!sourceSymbol) + { + auto sourceExpression = isExpression(sourceObject); + if (auto identifier = sourceExpression + ? sourceExpression.isIdentifierExp() : null) + { + Dsymbol scopeSymbol; + sourceSymbol = sc.search(identifier.loc, identifier.ident, scopeSymbol); + } + else if (sourceExpression) + { + if (sourceExpression.isTraitsExp()) + sourceSymbol = dmd.expressionsem.getDsymbol( + sourceExpression.expressionSemantic(sc)); + else + sourceSymbol = dmd.expressionsem.getDsymbol(sourceExpression); + } + } + while (sourceSymbol) + { + auto aliasDeclaration = sourceSymbol.isAliasDeclaration(); + if (!aliasDeclaration || !aliasDeclaration.aliassym) + break; + sourceSymbol = aliasDeclaration.aliassym; + } + EnumUnionDeclaration sourceEnum; + if (auto sourceTrait = isExpression(sourceObject) + ? isExpression(sourceObject).isTraitsExp() : null) + { + if (sourceTrait.ident == Id.getVariant && sourceTrait.args && sourceTrait.args.length == 2) + { + Objects enumArgument; + enumArgument.push((*sourceTrait.args)[0]); + if (!TemplateInstance_semanticTiargs(sourceTrait.loc, sc, &enumArgument, 0)) + return false; + auto enumType = dmd.dtemplate.getType(enumArgument[0]); + auto structType = enumType ? enumType.toBasetype().isTypeStruct() : null; + sourceEnum = structType ? structType.sym.isEnumUnionDeclaration() : null; + } + } + + Objects sourceArgument; + sourceArgument.push(sourceObject); + if (!TemplateInstance_semanticTiargs(trait.loc, sc, &sourceArgument, 0)) + return false; + sourceObject = sourceArgument[0]; + if (!sourceEnum && sourceSymbol) + { + auto parent = sourceSymbol.toParent2(); + sourceEnum = parent ? parent.isEnumUnionDeclaration() : null; + } + if (!sourceEnum) + { + sourceSymbol = dmd.expressionsem.getDsymbol(sourceObject); + if (sourceSymbol) + { + auto parent = sourceSymbol.toParent2(); + sourceEnum = parent ? parent.isEnumUnionDeclaration() : null; + } + } + if (!sourceEnum) + { + eSink.error(trait.loc, "argument `%s` is not an enum union variant", + sourceObject.toErrMsg()); + return false; + } + + auto sourceType = dmd.dtemplate.getType(sourceObject); + if (!sourceSymbol && sourceType) + sourceSymbol = sourceType.toDsymbol(sc); + auto resolvedSourceSymbol = sourceSymbol ? sourceSymbol.toAlias() : null; + EnumUnionVariant* sourceVariant; + foreach (ref variant; sourceEnum.variants) + { + bool matches; + if (variant.descriptor && sourceSymbol) + matches = variant.descriptor is sourceSymbol; + if (!matches && variant.declaration && resolvedSourceSymbol) + matches = variant.declaration == resolvedSourceSymbol; + if (!matches && variant.declaration && sourceSymbol) + matches = variant.declaration == sourceSymbol; + if (!matches && variant.ident && resolvedSourceSymbol) + { + auto factory = resolvedSourceSymbol.isFuncDeclaration(); + matches = factory && factory.isGenerated && factory.ident == variant.ident && + factory.toParent2() == sourceEnum; + } + if (!matches && !variant.ident && sourceType) + { + auto payloadType = variant.payloadType && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type + : variant.payload.length == 1 ? variant.payload[0] : null; + matches = payloadType && payloadType.toBasetype().equals(sourceType.toBasetype()); + } + if (matches) + { + sourceVariant = &variant; + break; + } + } + if (!sourceVariant) + { + eSink.error(trait.loc, "argument `%s` is not an enum union variant", + sourceObject.toErrMsg()); + return false; + } + + expanded = syntaxCopyEnumUnionVariant(*sourceVariant); + expanded.loc = splice.loc; + if (overrideName) + { + expanded.ident = overrideName; + if (!sourceVariant.ident) + expanded.isTypeAlias = true; + } + return true; +} + +private bool equivalentEnumUnionVariants(ref EnumUnionVariant left, + ref EnumUnionVariant right) +{ + bool equivalentExpressions(Expressions* leftExpressions, + Expressions* rightExpressions) + { + if (!leftExpressions || !rightExpressions) + return leftExpressions is rightExpressions; + if (leftExpressions.length != rightExpressions.length) + return false; + foreach (index; 0 .. leftExpressions.length) + if (!dmd.expressionsem.equals((*leftExpressions)[index], + (*rightExpressions)[index])) + return false; + return true; + } + + if (left.ident != right.ident || left.isTypeAlias != right.isTypeAlias || + !!left.members != !!right.members || left.payload.length != right.payload.length || + left.payloadNames.length != right.payloadNames.length || + !equivalentExpressions(left.udas, right.udas)) + return false; + + foreach (index; 0 .. left.payload.length) + if (!left.payload[index] || !right.payload[index] || + !left.payload[index].equals(right.payload[index])) + return false; + foreach (index; 0 .. left.payloadNames.length) + if (left.payloadNames[index] != right.payloadNames[index]) + return false; + + if (left.members) + { + VarDeclaration[] leftFields; + VarDeclaration[] rightFields; + foreach (member; *left.members) + if (auto field = member.isVarDeclaration()) + leftFields ~= field; + else + return false; + foreach (member; *right.members) + if (auto field = member.isVarDeclaration()) + rightFields ~= field; + else + return false; + if (leftFields.length != rightFields.length) + return false; + foreach (index; 0 .. leftFields.length) + if (leftFields[index].ident != rightFields[index].ident || + !leftFields[index].type || !rightFields[index].type || + !leftFields[index].type.equals(rightFields[index].type)) + return false; + } + return true; +} + +private void appendEnumUnionVariant(ref EnumUnionVariant[] variants, + EnumUnionVariant variant) +{ + foreach (ref existing; variants) + if (equivalentEnumUnionVariants(existing, variant)) + return; + variants ~= variant; +} + +private void collectEnumUnionCases(Dsymbols* symbols, Scope* sc, + ref EnumUnionVariant[] variants, Dsymbols* retained, Type forcedPayload = null, + bool discardLoopBindings = false, Dsymbol forcedSourceSymbol = null) +{ + auto eSink = global.errorSink; + if (!symbols) + return; + + foreach (d; *symbols) + { + if (auto caseDecl = d.isEnumUnionCaseDeclaration()) + { + auto variant = caseDecl.variant; + if (variant.variantSplice) + { + EnumUnionVariant expanded; + if (expandEnumUnionVariantSplice(variant, sc, expanded, + forcedSourceSymbol)) + appendEnumUnionVariant(variants, expanded); + continue; + } + variant.payload = variant.payload.dup; + if (variant.payload.length) + { + if (forcedPayload) + variant.payload[0] = forcedPayload; + else + { + const typeName = variant.payload[0].toErrMsg(); + auto resolvedType = trySemantic(variant.payload[0], caseDecl.loc, sc); + if (!resolvedType) + { + eSink.error(caseDecl.loc, + "unknown type `%s`; for a named unit variant, use `case %s()`", + typeName, typeName); + variant.payload[0] = Type.terror; + variants ~= variant; + continue; + } + variant.payload[0] = resolvedType; + } + } + variant.generated = forcedPayload !is null; + if (variant.generated) + { + foreach (existing; variants) + { + if (existing.generated && !existing.ident && existing.payload.length == 1 && + variant.payload.length == 1 && existing.payload[0].equals(variant.payload[0])) + goto skipGeneratedCase; + } + } + variants ~= variant; + skipGeneratedCase: + continue; + } + if (auto sif = d.isStaticIfDeclaration()) + { + auto conditionScope = sc; + if (sif._scope != conditionScope) + sif.setScope(conditionScope); + if (auto sic = sif.condition.isStaticIfCondition()) + { + sic.inc = Include.notComputed; + } + auto conditionResult = dmd.expressionsem.include(sif.condition, conditionScope); + auto selected = conditionResult ? sif.decl : sif.elsedecl; + collectEnumUnionCases(selected, conditionScope, variants, retained, + forcedPayload, discardLoopBindings); + continue; + } + if (auto sfd = d.isStaticForeachDeclaration()) + { + if (!sfd.cached) + { + sfd.sfe.prepare(sfd._scope); + dmd.dsymbolsem.include(sfd, sc); + } + if (sfd.cache) + { + foreach (i, expanded; *sfd.cache) + { + if (auto fad = expanded.isForwardingAttribDeclaration()) + { + auto iterationScope = sc.push(fad.sym); + fad.decl.foreachDsymbol(s => s.setScope(iterationScope)); + Type payload; + Dsymbol sourceSymbol; + foreach (member; *fad.decl) + { + if (auto loopAlias = member.isAliasDeclaration()) + { + payload = loopAlias.type; + if (!payload && loopAlias.aliassym) + payload = loopAlias.aliassym.isType(); + break; + } + } + if (sfd.sfe.aggrfe) + if (auto tuple = sfd.sfe.aggrfe.aggr.isTupleExp()) + if (i < tuple.exps.length) + sourceSymbol = dmd.expressionsem.getDsymbol( + (*tuple.exps)[i]); + collectEnumUnionCases(fad.decl, iterationScope, variants, retained, + payload, true, sourceSymbol); + } + } + } + continue; + } + if (auto fad = d.isForwardingAttribDeclaration()) + { + auto iterationScope = sc.push(fad.sym); + fad.decl.foreachDsymbol(s => s.setScope(iterationScope)); + collectEnumUnionCases(fad.decl, iterationScope, variants, retained, null, true); + continue; + } + if (discardLoopBindings && d.isPragmaDeclaration()) + { + d.setScope(sc); + d.dsymbolSemantic(sc); + continue; + } + if (discardLoopBindings && d.isAliasDeclaration()) + continue; + retained.push(d); + } +} + +private void synthesizeEnumUnionFactories(EnumUnionDeclaration eu, Scope* sc) +{ + auto eSink = global.errorSink; + if (eu.parent && eu.parent.isTemplateDeclaration()) + return; + if (eu.enumUnionFactoriesSynthesized) + return; + eu.enumUnionFactoriesSynthesized = true; + AnonDeclaration anon; + foreach (member; *eu.members) + if (auto candidate = member.isAnonDeclaration()) + if (candidate.isunion) + { + anon = candidate; + break; + } + if (!anon) + return; + bool hasErrors; + + Identifier variantName(ref EnumUnionVariant variant) + { + if (variant.ident) + return variant.ident; + if (variant.payload.length != 1) + return null; + + auto payloadType = variant.payload[0]; + if (auto identifierType = payloadType.isTypeIdentifier()) + return identifierType.ident; + payloadType = payloadType.toBasetype(); + if (auto structType = payloadType.isTypeStruct()) + return structType.sym.ident; + if (auto classType = payloadType.isTypeClass()) + return classType.sym.ident; + if (auto enumType = payloadType.isTypeEnum()) + return enumType.sym.ident; + return null; + } + + // Duplicate-case rule: no two variants (of any kind - unit, positional, + // or record) may share the same identifier, regardless of their payload + // types/signatures. Without this, two same-named variants with different + // signatures would just look like ordinary D function overloads to the + // synthesized factory functions, silently accepted instead of rejected. + foreach (i, variant; eu.variants) + { + auto ident = variantName(variant); + if (!ident) + continue; + bool isDup; + foreach (k; 0 .. i) + isDup = isDup || variantName(eu.variants[k]) == ident; + if (isDup) + continue; // already reported when this name was first seen + foreach (j; i + 1 .. eu.variants.length) + { + if (variantName(eu.variants[j]) == ident) + { + eSink.error(eu.loc, "duplicate case `%s` in enum union `%s`", + ident.toChars(), eu.toPrettyChars()); + hasErrors = true; + break; + } + } + } + if (hasErrors) + { + eu.errors = true; + return; + } + + foreach (i, ref variant; eu.variants) + { + if (variant.ident) + { + bool isDup; + foreach (k; 0 .. i) + isDup = isDup || variantName(eu.variants[k]) == variant.ident; + if (isDup) + continue; + } + + Dsymbol declaration; + if (variant.members) + { + auto payloadStruct = new StructDeclaration(variant.loc, variant.ident, false); + payloadStruct.members = variant.members; + declaration = payloadStruct; + } + else if (variant.isTypeAlias) + declaration = new AliasDeclaration(variant.loc, variant.ident, variant.payload[0]); + if (!declaration) + continue; + + variant.declaration = declaration; + Dsymbol member = declaration; + if (variant.udas) + member = new UserAttributeDeclaration(variant.udas, new Dsymbols(declaration)); + eu.members.push(member); + member.addMember(sc, eu); + member.setScope(sc); + member.importAll(sc); + member.dsymbolSemantic(sc); + if (declaration.errors) + hasErrors = true; + } + + VarDeclaration[] payloadVars; + Dsymbols* payloadMembers = new Dsymbols(); + StructDeclaration unitPayloadType; + foreach (ref variant; eu.variants) + { + auto payloadType = variant.payload.length ? variant.payload[0] : null; + if (!variant.ident && variant.payload.length == 1 && payloadType) + { + const typeName = payloadType.toErrMsg(); + auto resolvedType = trySemantic(payloadType, variant.loc, sc); + if (!resolvedType) + { + eSink.error(variant.loc, + "unknown type `%s`; for a named unit variant, use `case %s()`", + typeName, typeName); + hasErrors = true; + continue; + } + variant.payload[0] = resolvedType; + payloadType = resolvedType; + } + auto typeOf = payloadType ? payloadType.isTypeTypeof() : null; + if (payloadType && (payloadType.ty == Terror || typeOf && typeOf.exp.op == EXP.error)) + { + hasErrors = true; + continue; + } + const isUnitVariant = !variant.payload.length && !variant.members; + StructDeclaration payloadStruct; + if (auto recordDeclaration = variant.declaration + ? variant.declaration.isStructDeclaration() : null) + payloadStruct = recordDeclaration; + else if (isUnitVariant) + { + if (!unitPayloadType) + { + unitPayloadType = new StructDeclaration(eu.loc, + Identifier.generateId("__enumUnitPayload"), false); + unitPayloadType.members = new Dsymbols(); + unitPayloadType.parent = eu; + unitPayloadType.dsymbolSemantic(sc); + if (unitPayloadType.errors || unitPayloadType.type.ty == Terror) + { + hasErrors = true; + continue; + } + } + payloadStruct = unitPayloadType; + } + else + { + payloadStruct = new StructDeclaration(eu.loc, + Identifier.generateId("__enumVariantPayload"), false); + payloadStruct.members = variant.members; + if (!payloadStruct.members) + { + payloadStruct.members = new Dsymbols(); + foreach (k, payload; variant.payload) + { + auto ident = k < variant.payloadNames.length && variant.payloadNames[k] + ? variant.payloadNames[k] : Identifier.generateId("__enumPayload"); + payloadStruct.members.push(new VarDeclaration(eu.loc, payload, ident, null)); + } + } + payloadStruct.parent = eu; + payloadStruct.dsymbolSemantic(sc); + if (payloadStruct.errors || payloadStruct.type.ty == Terror) + { + hasErrors = true; + continue; + } + } + variant.payloadType = payloadStruct; + foreach (field; payloadStruct.fields) + { + if (field.type.ty == Terror) + { + hasErrors = true; + break; + } + } + if (hasErrors) + continue; + auto payloadVar = new VarDeclaration(eu.loc, new TypeStruct(payloadStruct), + Identifier.generateId("__enumPayload"), null); + variant.payloadVar = payloadVar; + payloadVars ~= payloadVar; + payloadMembers.push(payloadVar); + } + + // Move-only rule: a variant whose payload has a move constructor but no + // copy constructor cannot be safely stored, since ordinary copies of the + // enum union (e.g. assignment, passing by value) perform a raw bitcopy of + // the payload union rather than invoking the move constructor, leading to + // double-destruction of the payload. + foreach (variant; eu.variants) + { + if (!variant.payloadType) + continue; + foreach (field; variant.payloadType.fields) + { + auto fieldType = field.type.baseElemOf().isTypeStruct(); + if (!fieldType) + continue; + if (fieldType.sym.hasMoveCtor && !fieldType.sym.hasCopyCtor) + { + eSink.error(eu.loc, + "cannot create enum union with element type `%s` that has a move constructor but no copy constructor", + fieldType.sym.toChars()); + hasErrors = true; + } + } + } + + // Duplicate-type rule: unlabeled (bare-type) variants must have distinct + // payload types; ambiguous constructions like `case double, case double,` + // are rejected at compile time. + foreach (i, variant; eu.variants) + { + if (variant.ident || variant.payload.length != 1 || + !variant.payloadType || !variant.payloadType.fields.length) + continue; + auto typeI = variant.payloadType.fields[0].type; + foreach (j; i + 1 .. eu.variants.length) + { + auto other = eu.variants[j]; + if (other.ident || other.payload.length != 1 || + !other.payloadType || !other.payloadType.fields.length) + continue; + auto typeJ = other.payloadType.fields[0].type; + if (typeI.equals(typeJ)) + { + eSink.error(eu.loc, "duplicate case `%s` in enum union `%s`", + typeI.toErrMsg(), eu.toPrettyChars()); + hasErrors = true; + break; + } + } + } + if (hasErrors) + { + anon.decl = payloadMembers; + eu.errors = true; + return; // don't synthesize factories for a broken declaration + } + + anon.decl = payloadMembers; + foreach (m; *payloadMembers) + { + m.addMember(sc, eu); + m.setScope(sc); + m.importAll(sc); + } + + foreach (i, ref variant; eu.variants) + { + if (!variant.ident || variant.declaration) + continue; + + auto parameters = new Parameters(); + auto aliasPayloadType = variant.isTypeAlias && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type : null; + auto aliasStruct = aliasPayloadType ? aliasPayloadType.toBasetype().isTypeStruct() : null; + VarDeclaration[] aliasFields; + if (aliasStruct) + { + foreach (field; aliasStruct.sym.fields) + aliasFields ~= field; + if (!aliasFields.length && aliasStruct.sym.members) + foreach (member; *aliasStruct.sym.members) + if (auto field = member.isVarDeclaration()) + aliasFields ~= field; + } + const nfields = aliasStruct ? aliasFields.length + : variant.payloadType ? variant.payloadType.fields.length : 1; + foreach (k; 0 .. nfields) + { + auto pident = Identifier.generateId("__enumPayloadParam"); + if (k < variant.payloadNames.length && variant.payloadNames[k]) + pident = variant.payloadNames[k]; + else if (!aliasStruct && variant.payloadType && variant.payloadType.fields.length > k) + pident = variant.payloadType.fields[k].ident; + auto fieldType = aliasStruct ? aliasFields[k].type + : variant.payloadType ? variant.payloadType.fields[k].type : variant.payload[0]; + parameters.push(new Parameter(eu.loc, STC.none, fieldType, + pident, null, null, null)); + } + + const stc = nfields ? STC.none : STC.property; + auto functionType = new TypeFunction(ParameterList(parameters), eu.type, LINK.d, stc); + auto fd = new FuncDeclaration(eu.loc, eu.loc, variant.ident, STC.static_, functionType); + fd.isGenerated = true; + auto result = new VarDeclaration(eu.loc, eu.type, Identifier.generateId("__enumResult"), + new VoidInitializer(eu.loc)); + result.storage_class |= STC.nodtor; + auto arguments = new Expressions(); + foreach (parameter; *parameters) + arguments.push(new IdentifierExp(eu.loc, parameter.ident)); + if (aliasStruct) + { + auto literal = new StructLiteralExp(eu.loc, aliasStruct.sym, arguments, aliasPayloadType); + arguments = new Expressions(literal); + } + auto initialize = initializeEnumUnionVariant(eu.loc, result, eu, i, arguments); + Statements statements; + statements.push(new ExpStatement(eu.loc, result)); + statements.push(new ExpStatement(eu.loc, initialize)); + statements.push(new ReturnStatement(eu.loc, new VarExp(eu.loc, result))); + fd.fbody = new CompoundStatement(eu.loc, statements.move()); + Dsymbol factoryMember = fd; + if (variant.udas) + factoryMember = new UserAttributeDeclaration(variant.udas, new Dsymbols(fd)); + eu.members.push(factoryMember); + factoryMember.addMember(sc, eu); + factoryMember.setScope(sc); + factoryMember.importAll(sc); + variant.declaration = fd; + } +} + +private void synthesizeEnumUnionVariantDescriptors(EnumUnionDeclaration eu, Scope* sc) +{ + foreach (ref variant; eu.variants) + { + auto descriptor = new EnumUnionCaseDeclaration(variant.loc, + syntaxCopyEnumUnionVariant(variant)); + descriptor.ident = variant.ident; + descriptor.parent = eu; + descriptor._scope = sc; + if (auto declaration = variant.declaration + ? variant.declaration.isDeclaration() : null) + descriptor.type = declaration.type; + else if (auto aggregate = variant.declaration + ? variant.declaration.isAggregateDeclaration() : null) + descriptor.type = aggregate.type; + else + descriptor.type = variant.payloadType && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type + : variant.payload.length == 1 ? variant.payload[0] : null; + if (!eu.errors && !descriptor.ident && descriptor.type && + descriptor.type.ty != Terror) + if (auto payloadSymbol = descriptor.type.toDsymbol(sc)) + descriptor.ident = payloadSymbol.ident; + if (variant.udas) + { + descriptor.userAttribDecl = new UserAttributeDeclaration(variant.udas, null); + descriptor.userAttribDecl.setScope(sc); + } + variant.descriptor = descriptor; + } +} + +private void synthesizeEnumUnionConstructors(EnumUnionDeclaration eu, Scope* sc) +{ + Type[] synthesizedTypes; + foreach (i, variant; eu.variants) + { + if ((variant.ident && !variant.isTypeAlias) || variant.payload.length != 1 || !variant.payloadType || + !variant.payloadType.fields.length || !variant.payloadVar) + continue; + + auto field = variant.payloadType.fields[0]; + bool isDup = false; + foreach (st; synthesizedTypes) + { + if (st.equals(field.type)) + { + isDup = true; + break; + } + } + if (isDup) + continue; + synthesizedTypes ~= field.type; + + auto parameter = new Parameter(eu.loc, STC.none, field.type, + Identifier.generateId("__enumPayloadParam"), null, null, null); + auto parameters = new Parameters(parameter); + auto functionType = new TypeFunction(ParameterList(parameters), eu.type, LINK.d, STC.ref_); + auto ctor = new CtorDeclaration(eu.loc, eu.loc, STC.ref_, functionType); + ctor.isGenerated = true; + + Statements statements; + auto tagExp = new DotVarExp(eu.loc, new ThisExp(eu.loc), eu.tagVar); + statements.push(new ExpStatement(eu.loc, new AssignExp(eu.loc, tagExp, + new IntegerExp(eu.loc, i, Type.tuns8)))); + + Expression payloadExp = new DotVarExp(eu.loc, new ThisExp(eu.loc), variant.payloadVar); + payloadExp = new DotVarExp(eu.loc, payloadExp, field); + statements.push(new ExpStatement(eu.loc, new ConstructExp(eu.loc, payloadExp, + new IdentifierExp(eu.loc, parameter.ident)))); + + ctor.fbody = new CompoundStatement(eu.loc, statements.move()); + eu.members.push(ctor); + ctor.addMember(sc, eu); + + Scope* sc2 = sc.push(); + if (variant.udas) + sc2.userAttribDecl = new UserAttributeDeclaration(variant.udas, null); + sc2.stc = STC.none; + sc2.linkage = LINK.d; + ctor.dsymbolSemantic(sc2); + ctor.semantic2(sc2); + ctor.semantic3(sc2); + sc2.pop(); + } +} + +private void synthesizeEnumUnionCpCtor(EnumUnionDeclaration eu, Scope* sc) +{ + if (eu.hasCopyCtor) + return; + + bool hasElaborate; + foreach (variant; eu.variants) + { + if (!variant.payloadType) + continue; + foreach (field; variant.payloadType.fields) + { + auto ts = field.type.baseElemOf().isTypeStruct(); + if (!ts) + continue; + if (ts.sym.hasCopyCtor || ts.sym.postblit || ts.sym.dtor) + hasElaborate = true; + } + } + if (!hasElaborate) + return; + + auto rhsIdent = Identifier.generateId("__rhs"); + auto rhsParam = new Parameter(eu.loc, STC.ref_ | STC.return_ | STC.scope_, eu.type, + rhsIdent, null, null, null); + auto parameters = new Parameters(rhsParam); + auto functionType = new TypeFunction(ParameterList(parameters), eu.type, LINK.d, STC.ref_); + auto ctor = new CtorDeclaration(eu.loc, eu.loc, STC.ref_, functionType); + ctor.isGenerated = true; + + Statements statements; + auto thisTag = new DotVarExp(eu.loc, new ThisExp(eu.loc), eu.tagVar); + auto rhsTag = new DotVarExp(eu.loc, new IdentifierExp(eu.loc, rhsIdent), eu.tagVar); + statements.push(new ExpStatement(eu.loc, new AssignExp(eu.loc, thisTag, rhsTag))); + + CaseStatements cases; + foreach (i, variant; eu.variants) + { + if (!variant.payloadType || !variant.payloadVar) + continue; + Statements caseStatements; + auto thisPayload = new DotVarExp(eu.loc, new ThisExp(eu.loc), variant.payloadVar); + auto rhsPayload = new DotVarExp(eu.loc, new IdentifierExp(eu.loc, rhsIdent), variant.payloadVar); + + foreach (field; variant.payloadType.fields) + { + auto thisField = new DotVarExp(eu.loc, thisPayload, field); + auto rhsField = new DotVarExp(eu.loc, rhsPayload, field); + caseStatements.push(new ExpStatement(eu.loc, new ConstructExp(eu.loc, thisField, rhsField))); + } + caseStatements.push(new BreakStatement(eu.loc, null)); + auto body = new CompoundStatement(eu.loc, caseStatements.move()); + cases.push(new CaseStatement(eu.loc, + new IntegerExp(eu.loc, i, Type.tuns8), body)); + } + + Statements bodyStatements; + foreach (c; cases) + bodyStatements.push(c); + bodyStatements.push(new DefaultStatement(eu.loc, + new BreakStatement(eu.loc, null))); + auto switchStatement = new SwitchStatement(eu.loc, null, + new DotVarExp(eu.loc, new ThisExp(eu.loc), eu.tagVar), + new CompoundStatement(eu.loc, bodyStatements.move()), false, eu.loc); + statements.push(switchStatement); + ctor.fbody = new CompoundStatement(eu.loc, statements.move()); + + eu.members.push(ctor); + ctor.addMember(sc, eu); + + Scope* sc2 = sc.push(); + sc2.stc = STC.none; + sc2.linkage = LINK.d; + ctor.dsymbolSemantic(sc2); + ctor.semantic2(sc2); + ctor.semantic3(sc2); + sc2.pop(); + eu.hasCopyCtor = true; +} + +private void synthesizeEnumUnionDtor(EnumUnionDeclaration eu, Scope* sc) +{ + CaseStatements cases; + bool hasDtor; + foreach (i, variant; eu.variants) + { + if (!variant.payloadType) + continue; + + Statements caseStatements; + foreach (field; variant.payloadType.fields) + { + auto fieldType = field.type.baseElemOf().isTypeStruct(); + if (!fieldType || !fieldType.sym.dtor) + continue; + + hasDtor = true; + auto payload = new DotVarExp(Loc.initial, new ThisExp(Loc.initial), variant.payloadVar); + auto fieldExp = new DotVarExp(Loc.initial, payload, field); + auto call = new CallExp(Loc.initial, + new DotVarExp(Loc.initial, fieldExp, fieldType.sym.dtor, false)); + call.directcall = true; + caseStatements.push(new ExpStatement(Loc.initial, call)); + } + if (!caseStatements.length) + continue; + caseStatements.push(new BreakStatement(Loc.initial, null)); + auto body = new CompoundStatement(Loc.initial, caseStatements.move()); + cases.push(new CaseStatement(Loc.initial, + new IntegerExp(Loc.initial, i, Type.tuns8), body)); + } + if (!hasDtor) + return; + + Statements bodyStatements; + foreach (c; cases) + bodyStatements.push(c); + bodyStatements.push(new DefaultStatement(Loc.initial, + new BreakStatement(Loc.initial, null))); + auto switchStatement = new SwitchStatement(Loc.initial, null, + new DotVarExp(Loc.initial, new ThisExp(Loc.initial), eu.tagVar), + new CompoundStatement(Loc.initial, bodyStatements.move()), false, Loc.initial); + + if (eu.dtor) + { + if (auto cs = eu.dtor.fbody ? eu.dtor.fbody.isCompoundStatement() : null) + cs.statements.push(switchStatement); + else if (eu.dtor.fbody) + { + Statements stmts; + stmts.push(eu.dtor.fbody); + stmts.push(switchStatement); + eu.dtor.fbody = new CompoundStatement(eu.loc, stmts.move()); + } + else + eu.dtor.fbody = switchStatement; + return; + } + + Statements statements; + statements.push(switchStatement); + + auto dd = new DtorDeclaration(eu.loc, Loc.initial, STC.inference, Id.dtor); + dd.isGenerated = true; + dd.fbody = new CompoundStatement(Loc.initial, statements.move()); + eu.members.push(dd); + dd.addMember(sc, eu); + dd.dsymbolSemantic(sc); + eu.dtor = dd; +} + private extern(C++) final class DsymbolSemanticVisitor : Visitor { import dmd.typesem: size; @@ -2721,7 +3648,8 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor //printf("sc.stc = %x\n", sc.stc); //printf("storage_class = x%x\n", storage_class); - dsym.type.checkComplexTransition(dsym.loc, sc); + if (!sc.func || !sc.func.isGenerated) + dsym.type.checkComplexTransition(dsym.loc, sc); // Calculate type size + safety checks if (dsym.storage_class & STC.gshared && !dsym.isMember()) @@ -4511,6 +5439,16 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor TypeFunction tf = ctd.type.toTypeFunction(); immutable dim = tf.parameterList.length; auto sd = ad.isStructDeclaration(); + if (auto eu = ad.isEnumUnionDeclaration()) + { + if (!ctd.isGenerated && dim == 0 && tf.parameterList.varargs == VarArg.none) + { + eSink.error(ctd.loc, "enum union `%s` cannot have a no-argument constructor; use `.init` instead", + eu.toPrettyChars()); + ctd.errors = true; + return; + } + } /* See if it's the default constructor * But, template constructor should not become a default constructor. @@ -5027,6 +5965,65 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor */ sd.members.foreachDsymbol( s => s.setScope(sc2) ); sd.members.foreachDsymbol( s => s.importAll(sc2) ); + + if (auto eu = sd.isEnumUnionDeclaration()) + { + // Compile-time declarations must be expanded while the aggregate + // symbol table and forwarding scopes are live. Factory synthesis + // consumes the normalized variant list produced by this prepass. + if (!eu.enumUnionCasesExpanded) + { + eu.enumUnionCasesExpanded = true; + EnumUnionVariant[] expandedVariants; + foreach (ref variant; eu.variants) + { + if (!variant.variantSplice) + { + expandedVariants ~= variant; + continue; + } + EnumUnionVariant expanded; + if (expandEnumUnionVariantSplice(variant, sc2, expanded)) + appendEnumUnionVariant(expandedVariants, expanded); + } + eu.variants = expandedVariants; + auto originalMembers = eu.members; + auto compileTimeMembers = new Dsymbols(); + foreach (member; *originalMembers) + { + if (member !is eu.tagVar && !member.isAnonDeclaration()) + compileTimeMembers.push(member); + } + auto retainedMembers = new Dsymbols(); + collectEnumUnionCases(compileTimeMembers, sc2, eu.variants, retainedMembers); + eu.members = new Dsymbols(); + if (eu.tagVar) + eu.members.push(eu.tagVar); + if (originalMembers) + foreach (member; *originalMembers) + if (auto anon = member.isAnonDeclaration()) + if (anon.isunion) + { + eu.members.push(anon); + break; + } + eu.members.append(retainedMembers); + + eu.symtab = new DsymbolTable(); + eu.members.foreachDsymbol(s => s.addMember(sc, eu)); + eu.members.foreachDsymbol(s => s.setScope(sc2)); + eu.members.foreachDsymbol(s => s.importAll(sc2)); + + synthesizeEnumUnionFactories(eu, sc2); + synthesizeEnumUnionVariantDescriptors(eu, sc2); + + eu.symtab = new DsymbolTable(); + eu.members.foreachDsymbol(s => s.addMember(sc, eu)); + eu.members.foreachDsymbol(s => s.setScope(sc2)); + eu.members.foreachDsymbol(s => s.importAll(sc2)); + } + } + sd.members.foreachDsymbol( (s) { s.dsymbolSemantic(sc2); if (sd.errors) s.errors = true; } ); if (sd.errors) @@ -5069,10 +6066,18 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor */ sd.disableNew = sd.search(Loc.initial, Id.classNew) !is null; + if (auto eu = sd.isEnumUnionDeclaration()) + { + synthesizeEnumUnionConstructors(eu, sc2); + synthesizeEnumUnionCpCtor(eu, sc2); + } + // Look for the constructor sd.ctor = sd.searchCtor(); buildDtors(sd, sc2); + if (auto eu = sd.isEnumUnionDeclaration()) + synthesizeEnumUnionDtor(eu, sc2); bool hasCopyCtor; bool hasMoveCtor; diff --git a/compiler/src/dmd/expression.d b/compiler/src/dmd/expression.d index 1c498a64c29e..9caa996ee687 100644 --- a/compiler/src/dmd/expression.d +++ b/compiler/src/dmd/expression.d @@ -20,6 +20,7 @@ import core.stdc.string; import dmd.arraytypes; import dmd.astenums; import dmd.ast_node; +import dmd.cond; import dmd.dclass; import dmd.declaration; import dmd.dstruct; @@ -449,6 +450,7 @@ extern (C++) abstract class Expression : ASTNode inout(EqualExp) isEqualExp() { return (op == EXP.equal || op == EXP.notEqual) ? cast(typeof(return))this : null; } inout(IdentityExp) isIdentityExp() { return (op == EXP.identity || op == EXP.notIdentity) ? cast(typeof(return))this : null; } inout(CondExp) isCondExp() { return op == EXP.question ? cast(typeof(return))this : null; } + inout(SwitchExp) isSwitchExp() { return op == EXP.switchExpression ? cast(typeof(return))this : null; } inout(GenericExp) isGenericExp() { return op == EXP._Generic ? cast(typeof(return))this : null; } inout(DefaultInitExp) isDefaultInitExp() { return op == EXP.defaultInit ? cast(typeof(return))this : null; } inout(ObjcClassReferenceExp) isObjcClassReferenceExp() { return op == EXP.objcClassReference ? cast(typeof(return))this : null; } @@ -805,6 +807,7 @@ extern (C++) final class DsymbolExp : Expression { Dsymbol s; bool hasOverloads; + bool preserveSymbol; extern (D) this(Loc loc, Dsymbol s, bool hasOverloads = true) @safe { @@ -3781,6 +3784,100 @@ extern (C++) final class CondExp : BinExp } } +/*********************************************************** + * Expression-position switch with pattern/action arms. + */ +struct CaseExpArm +{ + Loc loc; + Expression pattern; + Type typePattern; + Identifier typeBinding; + Identifier[] recordBindings; + bool hasRestPattern; + Identifier[] recordPatternNames; + Expression[] recordPatterns; + Identifier restBinding; + Expression guard; + bool isDefault; + Expression action; + VarDeclaration[] bindings; + size_t variantIndex; + bool hasVariant; + + StaticForeach sfe; + CaseExpArm[] nestedArms; + Condition staticIfCond; + CaseExpArm[] elseArms; + ScopeDsymbol[] iterationScopes; + + CaseExpArm syntaxCopy() + { + CaseExpArm copy = this; + copy.pattern = pattern ? pattern.syntaxCopy() : null; + copy.typePattern = typePattern ? typePattern.syntaxCopy() : null; + copy.recordBindings = recordBindings.dup; + copy.recordPatternNames = recordPatternNames.dup; + if (recordPatterns) + { + copy.recordPatterns = new Expression[](recordPatterns.length); + foreach (j, pat; recordPatterns) + copy.recordPatterns[j] = pat ? pat.syntaxCopy() : null; + } + copy.bindings = null; + copy.hasVariant = false; + copy.variantIndex = 0; + copy.guard = guard ? guard.syntaxCopy() : null; + copy.action = action ? action.syntaxCopy() : null; + copy.iterationScopes = null; + if (sfe) + copy.sfe = sfe.syntaxCopy(); + if (nestedArms) + { + copy.nestedArms = new CaseExpArm[](nestedArms.length); + foreach (j, ref na; nestedArms) + copy.nestedArms[j] = na.syntaxCopy(); + } + if (staticIfCond) + copy.staticIfCond = staticIfCond.syntaxCopy(); + if (elseArms) + { + copy.elseArms = new CaseExpArm[](elseArms.length); + foreach (j, ref ea; elseArms) + copy.elseArms[j] = ea.syntaxCopy(); + } + return copy; + } +} + +extern (C++) final class SwitchExp : Expression +{ + Expression condition; + CaseExpArm[] arms; + bool hasDefault; + + extern (D) this(Loc loc, Expression condition, CaseExpArm[] arms, bool hasDefault) + { + super(loc, EXP.switchExpression); + this.condition = condition; + this.arms = arms; + this.hasDefault = hasDefault; + } + + override SwitchExp syntaxCopy() + { + auto copiedArms = new CaseExpArm[](arms.length); + foreach (i, arm; arms) + copiedArms[i] = arm.syntaxCopy(); + return new SwitchExp(loc, condition.syntaxCopy(), copiedArms, hasDefault); + } + + override void accept(Visitor v) + { + v.visit(this); + } +} + /*********************************************************** * A special keyword when used as a function's default argument * @@ -4126,6 +4223,7 @@ alias ExpOpTypePairs = AliasSeq! OpType!(EXP.dot, DotExp), OpType!(EXP.comma, CommaExp), OpType!(EXP.question, CondExp), + OpType!(EXP.switchExpression, SwitchExp), OpType!(EXP.andAnd, LogicalExp), OpType!(EXP.orOr, LogicalExp), OpType!(EXP.prePlusPlus, PreExp), diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index fe8eb92f3d57..777d4ce049a3 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -37,6 +37,7 @@ import dmd.declaration; import dmd.dclass; import dmd.dcast; import dmd.delegatize; +import dmd.decisiontree; import dmd.denum; import dmd.deps; import dmd.dimport; @@ -110,6 +111,8 @@ void merge(Scope* _this, Loc loc, const ref CtorFlow ctorflow) { auto eSink = global.errorSink; + mergeThisInitialized(_this.ctorflow.thisInitialized, ctorflow.thisInitialized, + _this.ctorflow.callSuper, ctorflow.callSuper); if (!mergeCallSuper(_this.ctorflow.callSuper, ctorflow.callSuper)) eSink.error(loc, "one path skips constructor"); @@ -2829,6 +2832,13 @@ Lagain: } } + if (auto variant = s.isEnumUnionCaseDeclaration()) + { + auto result = new DsymbolExp(loc, variant, false); + result.type = variant.type; + result.preserveSymbol = true; + return result; + } if (auto em = s.isEnumMember()) { return em.getVarExp(loc, sc); @@ -5384,6 +5394,8 @@ Dsymbol getDsymbol(RootObject oarg) if (auto ea = isExpression(oarg)) { // Try to convert Expression to symbol + if (auto de = ea.isDsymbolExp()) + return de.s; if (auto ve = ea.isVarExp()) return ve.var; if (auto fe = ea.isFuncExp()) @@ -5794,6 +5806,11 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor override void visit(DsymbolExp e) { + if (e.preserveSymbol) + { + result = e; + return; + } result = symbolToExp(e.s, e.loc, sc, e.hasOverloads); } @@ -5807,6 +5824,17 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor FuncDeclaration fd = hasThis(sc); // fd is the uplevel function with the 'this' variable AggregateDeclaration ad; + if (fd && fd.isCtorDeclaration() && !fd.isGenerated && !sc.allowUninitializedThis && + !sc.ctorflow.thisInitialized && !(sc.ctorflow.callSuper & CSX.this_ctor)) + { + if (auto eu = fd.isMemberLocal().isEnumUnionDeclaration()) + { + eSink.error(e.loc, "cannot read `this` in constructor `%s` before it is initialized", + fd.toPrettyChars()); + return setError(); + } + } + /* Special case for typeof(this) and typeof(super) since both * should work even if they are not inside a non-static member function */ @@ -7086,6 +7114,12 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } else { + if (sd.isEnumUnionDeclaration()) + { + eSink.error(exp.loc, "enum union `%s` cannot be constructed directly; use a variant", + sd.toPrettyChars()); + return setError(); + } if (exp.names) { exp.arguments = resolveStructLiteralNamedArgs(sd, exp.type, sc, exp.loc, exp.names.length, @@ -7698,6 +7732,95 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor eSink.errorSupplemental(loc, "%s `%s` is also known as `%s`", kind, errMsg, ie.ident.toChars()); } +private EnumUnionDeclaration resolveEnumUnionTemplateInstance(TemplateInstance ti, Scope* sc) +{ + if (!ti) + return null; + TemplateDeclaration td = ti.tempdecl ? ti.tempdecl.isTemplateDeclaration() : null; + if (!td && !ti.tempdecl && sc) + { + Dsymbol scopesym; + Dsymbol s = sc.search(ti.loc, ti.name, scopesym); + if (s) + td = s.toAlias().isTemplateDeclaration(); + } + if (td) + { + Dsymbol m = td.onemember ? td.onemember : (td.members && td.members.length == 1 ? (*td.members)[0] : null); + if (m && m.isEnumUnionDeclaration()) + { + if (!ti.tempdecl) + { + const errors = global.startGagging(); + ti.findTempDecl(sc, null); + if (global.endGagging(errors)) + return null; + } + const errors = global.startGagging(); + ti.dsymbolSemantic(sc); + if (global.endGagging(errors)) + return null; + if (ti.inst) + { + Dsymbol a = ti.toAlias(); + if (a) + return a.isEnumUnionDeclaration(); + } + } + } + return null; +} + +private EnumUnionDeclaration findEnumUnionFromExp(Expression e, Scope* sc) +{ + if (!e) + return null; + if (auto te = e.isTypeExp()) + { + if (auto st = te.type.toBasetype().isTypeStruct()) + return st.sym.isEnumUnionDeclaration(); + } + else if (auto se = e.isScopeExp()) + { + if (auto eu = se.sds.isEnumUnionDeclaration()) + return eu; + if (auto ti = se.sds.isTemplateInstance()) + return resolveEnumUnionTemplateInstance(ti, sc); + } + else if (auto ide = e.isIdentifierExp()) + { + Dsymbol scopesym; + Dsymbol s = sc.search(ide.loc, ide.ident, scopesym); + if (s) + { + s = s.toAlias(); + if (auto eu = s.isEnumUnionDeclaration()) + return eu; + if (auto t = dmd.dsymbolsem.getType(s)) + { + if (auto st = t.toBasetype().isTypeStruct()) + return st.sym.isEnumUnionDeclaration(); + } + } + } + else if (auto die = e.isDotIdExp()) + { + Dsymbol s = getDsymbol(die); + if (s) + { + s = s.toAlias(); + if (auto eu = s.isEnumUnionDeclaration()) + return eu; + if (auto t = dmd.dsymbolsem.getType(s)) + { + if (auto st = t.toBasetype().isTypeStruct()) + return st.sym.isEnumUnionDeclaration(); + } + } + } + return null; +} + override void visit(CallExp exp) { static if (LOGSEMANTIC) @@ -7714,6 +7837,29 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } scope(exit) --nest; + if (auto identifier = exp.e1.isIdentifierExp()) + { + Dsymbol scopesym; + sc.search(identifier.loc, identifier.ident, scopesym); + auto withsym = scopesym ? scopesym.isWithScopeSymbol() : null; + auto typeExp = withsym && !withsym.withstate.wthis + ? withsym.withstate.exp.isTypeExp() : null; + auto structType = typeExp ? typeExp.type.toBasetype().isTypeStruct() : null; + auto enumUnion = structType ? structType.sym.isEnumUnionDeclaration() : null; + if (enumUnion) + { + foreach (variant; enumUnion.variants) + { + if (variant.isTypeAlias && variant.ident == identifier.ident) + { + exp.e1 = new DotIdExp(identifier.loc, + new TypeExp(identifier.loc, typeExp.type), identifier.ident); + break; + } + } + } + } + scope (exit) { if (TypeFunction tf = exp.f && exp.f.type ? exp.f.type.isTypeFunction() : null) @@ -7744,6 +7890,62 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } } + if (auto dot = exp.e1.isDotIdExp()) + { + EnumUnionDeclaration enumUnion = findEnumUnionFromExp(dot.e1, sc); + + if (enumUnion) + { + foreach (variantIndex, variant; enumUnion.variants) + { + if (!variant.isTypeAlias || variant.ident != dot.ident) + continue; + auto payloadType = variant.payloadType && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type : variant.payload[0]; + Expression argument = null; + if (exp.arguments && exp.arguments.length == 1) + { + auto arg = (*exp.arguments)[0].expressionSemantic(sc); + arg = resolveProperties(sc, arg); + if (arg.implicitConvTo(payloadType) != MATCH.nomatch) + argument = arg.implicitCastTo(sc, payloadType); + } + if (!argument) + { + if (payloadType.toBasetype().isTypeStruct()) + { + auto call = new CallExp(exp.loc, new TypeExp(exp.loc, payloadType), exp.arguments); + argument = call.expressionSemantic(sc); + if (argument.op == EXP.error) + return setError(); + } + else + { + if (!exp.arguments || exp.arguments.length != 1) + { + eSink.error(exp.loc, "enum union alias variant `%s` expects one argument", + variant.ident.toChars()); + return setError(); + } + auto arg = (*exp.arguments)[0].expressionSemantic(sc); + arg = resolveProperties(sc, arg); + if (arg.implicitConvTo(payloadType) == MATCH.nomatch) + { + eSink.error(arg.loc, + "cannot implicitly convert expression `%s` of type `%s` to `%s`", + arg.toErrMsg(), arg.type.toErrMsg(), payloadType.toErrMsg()); + return setError(); + } + argument = arg.implicitCastTo(sc, payloadType); + } + } + result = constructEnumUnionVariant(argument, sc, enumUnion.type, + enumUnion, variantIndex); + return; + } + } + } + Objects* tiargs = null; // initial list of template arguments Expression ethis = null; Type tthis = null; @@ -7997,6 +8199,13 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (t1.ty == Tstruct) { auto sd = (cast(TypeStruct)t1).sym; + if (exp.e1.op == EXP.type && sd.isEnumUnionDeclaration() && + (!exp.arguments || exp.arguments.length == 0)) + { + eSink.error(exp.loc, "enum union `%s` cannot be constructed with no arguments; use `.init` instead", + sd.toPrettyChars()); + return setError(); + } sd.size(exp.loc); // Resolve forward references to construct object if (sd.sizeok != Sizeok.done) return setError(); @@ -8092,6 +8301,12 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor /* It's a struct literal */ Lx: + if (sd.isEnumUnionDeclaration()) + { + eSink.error(exp.loc, "enum union `%s` cannot be constructed directly; use a variant", + sd.toPrettyChars()); + return setError(); + } Expressions* resolvedArgs = exp.arguments; if (exp.names) { @@ -9251,7 +9466,13 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor sc2.minst = null; sc2.fullinst = true; sc2.traitsCompiles = true; + auto variantTraits = e.targ.isTypeTraits(); Type t = dmd.typesem.trySemantic(e.targ, e.loc, sc2); + if (!t && variantTraits) + if (auto variant = variantTraits.obj + ? variantTraits.obj.isDsymbol() : null) + if (auto descriptor = variant.isEnumUnionCaseDeclaration()) + t = descriptor.type; sc2.pop(); if (!t) // errors, so condition is false return no(); @@ -9278,6 +9499,14 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor tded = e.targ; break; + case TOK.enumUnion: + if (e.targ.ty != Tstruct) + return no(); + if (!(cast(TypeStruct)e.targ).sym.isEnumUnionDeclaration()) + return no(); + tded = e.targ; + break; + case TOK.class_: if (e.targ.ty != Tclass) return no(); @@ -12271,6 +12500,10 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor Expression e1old = exp.e1; + const assignsThis = exp.op == EXP.assign && exp.e1.isThisExp() && + sc.func && sc.func.isCtorDeclaration() && + sc.func.isMemberLocal().isEnumUnionDeclaration(); + if (auto e2comma = exp.e2.isCommaExp()) { if (!e2comma.isGenerated && !sc.inCfile) @@ -12400,6 +12633,10 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor /* Run this.e1 semantic. */ { + const allowUninitializedThis = sc.allowUninitializedThis; + if (assignsThis) + sc.allowUninitializedThis = true; + scope (exit) sc.allowUninitializedThis = allowUninitializedThis; Expression e1x = exp.e1; /* With UFCS, e.f = value @@ -13532,6 +13769,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor ce.e2 = lowerArrayAssign(ae2, true); } + if (assignsThis && !res.isErrorExp()) + sc.ctorflow.thisInitialized = true; return setResult(res); } @@ -15565,6 +15804,821 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = exp; } + private void expandSwitchExpArms(SwitchExp exp) + { + CaseExpArm[] flattened; + bool hasDefault = exp.hasDefault; + + void expandArms(ref CaseExpArm[] dest, CaseExpArm[] src, Scope* curScope, ScopeDsymbol[] currentIterScopes) + { + foreach (ref arm; src) + { + if (arm.sfe) + { + prepare(arm.sfe, curScope); + import dmd.statementsem : ready; + if (!arm.sfe.ready()) + continue; + + auto fs = arm.sfe.aggrfe; + if (!fs) + continue; + + size_t n; + TupleExp te = fs.aggr.isTupleExp(); + TypeTuple tuple = fs.aggr.type ? fs.aggr.type.toBasetype().isTypeTuple() : null; + if (te) + n = te.exps.length; + else if (fs.aggr.isTypeExp() && tuple) + n = Parameter.dim(tuple.arguments); + else + continue; + + const dim = fs.parameters.length; + if (dim < 1 || dim > 2) + { + eSink.error(arm.sfe.loc, + "only one (element) or two (index, element) arguments allowed for `static foreach`, not %llu", + cast(ulong)dim); + continue; + } + + foreach (j; 0 .. n) + { + size_t k = (fs.op == TOK.foreach_) ? j : n - 1 - j; + auto fwd = new ForwardingScopeDsymbol(); + fwd.symtab = new DsymbolTable(); + fwd.parent = curScope.scopesym ? curScope.scopesym : curScope.parent; + Scope* iterScope = curScope.push(fwd); + + if (dim == 2) + { + auto pIndex = (*fs.parameters)[0]; + Type ptype = pIndex.type ? pIndex.type : Type.tsize_t; + ptype = ptype.typeSemantic(arm.sfe.loc, iterScope); + auto ie = new ExpInitializer(arm.sfe.loc, new IntegerExp(arm.sfe.loc, k, ptype)); + auto idxVar = new VarDeclaration(arm.sfe.loc, ptype, pIndex.ident, ie); + idxVar.storage_class |= STC.manifest | STC.local; + idxVar.dsymbolSemantic(iterScope); + iterScope.insert(idxVar); + } + + auto pValue = (*fs.parameters)[dim - 1]; + Expression elemExp = te ? (*te.exps)[k] : null; + Type elemType = (!te && tuple) ? Parameter.getNth(tuple.arguments, k).type : null; + + Declaration var; + if (elemExp) + { + Type tb = elemExp.type ? elemExp.type.toBasetype() : null; + Dsymbol ds = null; + if (tb && tb.ty == Tfunction) + { + if (auto de = elemExp.isDsymbolExp()) ds = de.s; + else if (auto ve = elemExp.isVarExp()) ds = ve.var; + } + if (!ds) + { + if (auto de = elemExp.isDsymbolExp()) ds = de.s; + else if (auto ve = elemExp.isVarExp()) ds = ve.var; + else if (auto dve = elemExp.isDotVarExp()) ds = dve.var; + else if (auto te2 = elemExp.isTemplateExp()) ds = te2.td; + else if (auto se = elemExp.isScopeExp()) ds = se.sds; + else if (auto fe = elemExp.isFuncExp()) ds = fe.td ? fe.td : fe.fd; + else if (auto oe = elemExp.isOverExp()) ds = oe.vars; + } + if (auto typeExp = elemExp.isTypeExp()) + { + var = new AliasDeclaration(arm.sfe.loc, pValue.ident, typeExp.type); + } + else if (ds) + { + var = new AliasDeclaration(arm.sfe.loc, pValue.ident, ds); + } + else + { + elemExp = resolveProperties(iterScope, elemExp); + auto ie = new ExpInitializer(arm.sfe.loc, elemExp); + Type vtype = pValue.type ? pValue.type.typeSemantic(arm.sfe.loc, iterScope) : elemExp.type; + auto vd = new VarDeclaration(arm.sfe.loc, vtype, pValue.ident, ie, pValue.storageClass); + vd.storage_class |= STC.manifest; + var = vd; + } + } + else if (elemType) + { + var = new AliasDeclaration(arm.sfe.loc, pValue.ident, elemType); + } + if (var) + { + var.storage_class |= STC.local; + var.dsymbolSemantic(iterScope); + iterScope.insert(var); + } + + ScopeDsymbol[] nextIterScopes = currentIterScopes ~ fwd; + CaseExpArm[] iterationArms; + foreach (ref na; arm.nestedArms) + iterationArms ~= na.syntaxCopy(); + expandArms(dest, iterationArms, iterScope, nextIterScopes); + + iterScope.pop(); + } + } + else if (arm.staticIfCond) + { + if (include(arm.staticIfCond, curScope)) + { + CaseExpArm[] copied; + foreach (ref na; arm.nestedArms) + copied ~= na.syntaxCopy(); + expandArms(dest, copied, curScope, currentIterScopes); + } + else if (arm.elseArms.length) + { + CaseExpArm[] copied; + foreach (ref ea; arm.elseArms) + copied ~= ea.syntaxCopy(); + expandArms(dest, copied, curScope, currentIterScopes); + } + } + else + { + if (arm.isDefault) + hasDefault = true; + arm.iterationScopes = currentIterScopes.dup; + dest ~= arm; + } + } + } + + expandArms(flattened, exp.arms, sc, null); + exp.arms = flattened; + exp.hasDefault = hasDefault; + } + + override void visit(SwitchExp exp) + { + Expression conditionPrefix; + exp.condition = exp.condition.expressionSemantic(sc); + exp.condition = resolveProperties(sc, exp.condition); + if (exp.condition.op == EXP.error) + return setError(); + + if (auto conditionType = exp.condition.type.toBasetype().isTypeStruct()) + { + auto parentEnumUnion = conditionType.sym.parent + ? conditionType.sym.parent.isEnumUnionDeclaration() : null; + bool isRecordVariant; + if (parentEnumUnion) + foreach (variant; parentEnumUnion.variants) + isRecordVariant = isRecordVariant || variant.declaration == conditionType.sym; + if (isRecordVariant) + exp.condition = exp.condition.implicitCastTo(sc, parentEnumUnion.type); + } + auto conditionVar = exp.condition.isVarExp(); + if (!conditionVar || conditionVar.var.storage_class & STC.manifest) + { + auto vd = copyToTemp(STC.rvalue, "__switch", exp.condition); + conditionPrefix = Expression.combine(conditionPrefix, + new DeclarationExp(vd.loc, vd).expressionSemantic(sc)); + exp.condition = new VarExp(vd.loc, vd).expressionSemantic(sc); + } + + expandSwitchExpArms(exp); + + EnumUnionDeclaration enumUnion; + if (auto ts = exp.condition.type.toBasetype().isTypeStruct()) + enumUnion = ts.sym.isEnumUnionDeclaration(); + + Type resultType; + bool semanticDefaultSeen; + foreach (ref arm; exp.arms) + { + if (arm.isDefault) + { + if (semanticDefaultSeen) + { + eSink.error(arm.loc, "duplicate `default` arm in switch expression"); + return setError(); + } + semanticDefaultSeen = true; + } + Scope* iterScope = sc; + foreach (sds; arm.iterationScopes) + iterScope = iterScope.push(sds); + Scope* armScope = iterScope.push(new ScopeDsymbol()); + scope (exit) + { + armScope.pop(); + for (size_t i = 0; i < arm.iterationScopes.length; ++i) + iterScope = iterScope.pop(); + } + if (!arm.isDefault && enumUnion) + { + auto eu = enumUnion; + { + bool addPatternBinding(VarDeclaration variable) + { + variable.dsymbolSemantic(armScope); + if (!armScope.insert(variable)) + { + eSink.error(variable.loc, "duplicate pattern binding `%s`", variable.ident.toChars()); + return false; + } + arm.bindings ~= variable; + return true; + } + + bool bindUnpack(UnpackDeclaration unpack, Expression initializer) + { + unpack._init = initializer; + import dmd.dsymbolsem : include; + + bool appendBindings(Dsymbols* declarations) + { + if (!declarations) + return false; + foreach (declaration; *declarations) + { + if (auto variable = declaration.isVarDeclaration()) + { + if (!addPatternBinding(variable)) + return false; + } + else if (auto nested = declaration.isUnpackDeclaration()) + { + if (!appendBindings(nested.include(armScope))) + return false; + } + } + return true; + } + + return appendBindings(unpack.include(armScope)); + } + + Identifier variantId; + Expressions* arguments; + ArgumentLabels* argumentNames; + bool isCallPattern; + bool bindsNamedVariant; + if (arm.pattern) + { + if (auto call = arm.pattern.isCallExp()) + { + isCallPattern = true; + argumentNames = call.names; + if (auto id = call.e1.isIdentifierExp()) + variantId = id.ident; + else if (auto dot = call.e1.isDotIdExp()) + { + auto callee = call.e1.expressionSemantic(armScope); + if (callee.op == EXP.error) + return setError(); + variantId = dot.ident; + } + arguments = call.arguments; + } + else if (auto id = arm.pattern.isIdentifierExp()) + variantId = id.ident; + else if (auto dot = arm.pattern.isDotIdExp()) + { + auto qualified = arm.pattern.expressionSemantic(armScope); + if (qualified.op == EXP.error) + return setError(); + if (auto typeExp = qualified.isTypeExp()) + arm.typePattern = typeExp.type; + else + variantId = dot.ident; + } + } + + if (arm.typePattern && arm.typeBinding) + { + if (auto typeId = arm.typePattern.isTypeIdentifier()) + { + foreach (variant; eu.variants) + { + if (variant.ident == typeId.ident) + { + variantId = typeId.ident; + arm.typePattern = null; + bindsNamedVariant = true; + break; + } + } + } + } + + if (arm.typePattern) + arm.typePattern = arm.typePattern.typeSemantic(arm.loc, armScope); + + // An identifier-only arm first denotes a named variant. If + // there is no such variant, resolve it as an unbound bare + // payload type (e.g. `case MyStruct =>`). + if (!arm.typePattern && variantId && !isCallPattern) + { + bool hasNamedVariant; + foreach (variant; eu.variants) + hasNamedVariant = hasNamedVariant || variant.ident == variantId; + if (!hasNamedVariant) + { + auto typePattern = new TypeIdentifier(arm.loc, variantId); + arm.typePattern = typePattern.typeSemantic(arm.loc, armScope); + } + } + + if (!arm.typePattern && !variantId) + { + if (isCallPattern) + eSink.error(arm.loc, "switch expression call pattern requires a named variant callee"); + else + eSink.error(arm.loc, "switch expression value and expression patterns are not supported; use a named variant or type pattern"); + return setError(); + } + + foreach (variantIndex, variant; eu.variants) + { + if (arm.typePattern) + { + auto payloadType = variant.payloadType && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type + : variant.payload.length ? variant.payload[0] : null; + if ((variant.ident && !variant.isTypeAlias) || variant.payload.length != 1 || !payloadType || + !payloadType.equals(arm.typePattern)) + continue; + } + else if (variant.ident != variantId) + continue; + arm.hasVariant = true; + arm.variantIndex = variantIndex; + if (arm.typeBinding) + { + auto variable = new VarDeclaration(arm.loc, + bindsNamedVariant ? variant.payloadVar.type : arm.typePattern, + arm.typeBinding, null); + if (bindsNamedVariant) + { + auto payloadVar = variant.payloadVar; + auto payload = new DotVarExp(arm.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + if (variant.isTypeAlias && variant.payloadType && variant.payloadType.fields.length) + { + auto field = variant.payloadType.fields[0]; + auto value = new DotVarExp(arm.loc, payload, field); + value.type = field.type; + variable = new VarDeclaration(arm.loc, field.type, arm.typeBinding, + new ExpInitializer(arm.loc, value)); + } + else if (!variant.declaration && variant.payloadType && variant.payloadType.fields.length == 1) + { + auto field = variant.payloadType.fields[0]; + auto value = new DotVarExp(arm.loc, payload, field); + value.type = field.type; + variable = new VarDeclaration(arm.loc, field.type, arm.typeBinding, + new ExpInitializer(arm.loc, value)); + } + else + { + variable._init = new ExpInitializer(arm.loc, payload); + } + } + else + { + auto payloadVar = variant.payloadVar; + auto payload = new DotVarExp(arm.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + if (variant.payloadType && variant.payloadType.fields.length) + { + auto field = variant.payloadType.fields[0]; + auto value = new DotVarExp(arm.loc, payload, field); + value.type = field.type; + variable._init = new ExpInitializer(arm.loc, value); + } + else if (variant.payload.length == 1) + { + auto value = new DotVarExp(arm.loc, exp.condition, payloadVar); + value.type = payloadVar.type; + variable._init = new ExpInitializer(arm.loc, value); + } + } + variable.storage_class |= STC.temp | STC.ctfe; + if (!addPatternBinding(variable)) + return setError(); + } + else if (arguments) + { + const fieldCount = variant.payloadType ? variant.payloadType.fields.length : 0; + if (arm.hasRestPattern) + { + if (arm.restBinding) + { + Expressions* restValues = new Expressions(); + auto payloadVar = variant.payloadVar; + auto payload = new DotVarExp(arm.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + foreach (field; variant.payloadType.fields) + { + auto fieldValue = new DotVarExp(arm.loc, payload, field); + fieldValue.type = field.type; + restValues.push(fieldValue); + } + auto rest = new VarDeclaration(arm.loc, null, arm.restBinding, + new ExpInitializer(arm.loc, new TupleExp(arm.loc, restValues))); + rest.storage_class |= STC.temp | STC.ctfe; + if (!addPatternBinding(rest)) + return setError(); + } + break; + } + if (arguments.length != fieldCount) + { + eSink.error(arm.loc, + "pattern for variant `%s` has %llu argument(s), expected %llu", + variant.ident.toChars(), cast(ulong) arguments.length, cast(ulong) fieldCount); + return setError(); + } + bool[] used = new bool[](fieldCount); + size_t nextField; + foreach (i, argument; *arguments) + { + auto label = argumentNames && i < argumentNames.length + ? (*argumentNames)[i].name : null; + size_t fieldIndex = size_t.max; + if (label) + { + foreach (j, field; variant.payloadType.fields) + if (field.ident == label) + { + fieldIndex = j; + break; + } + } + else + { + while (nextField < used.length && used[nextField]) + ++nextField; + if (nextField < used.length) + fieldIndex = nextField++; + } + if (fieldIndex == size_t.max || used[fieldIndex]) + { + eSink.error(arm.loc, "no such or duplicate field `%s` in variant pattern", + label ? label.toChars() : ""); + return setError(); + } + used[fieldIndex] = true; + auto field = variant.payloadType.fields[fieldIndex]; + auto payloadVar = variant.payloadVar; + auto payload = new DotVarExp(argument.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + auto value = new DotVarExp(argument.loc, payload, field); + value.type = exp.condition.type ? field.type.addMod(exp.condition.type.mod) : field.type; + if (auto declaration = argument.isDeclarationExp()) + { + auto unpack = declaration.declaration.isUnpackDeclaration(); + if (!unpack || !bindUnpack(unpack, value)) + return setError(); + continue; + } + auto binding = argument.isIdentifierExp(); + if (!binding) + { + eSink.error(argument.loc, + "value tests in patterns are not supported; bind the value and use an `if` guard"); + return setError(); + } + auto qualifiedType = exp.condition.type ? field.type.addMod(exp.condition.type.mod) : field.type; + auto variable = new VarDeclaration(binding.loc, qualifiedType, + binding.ident, null); + value.loc = binding.loc; + value.type = qualifiedType; + variable._init = new ExpInitializer(binding.loc, value); + variable.storage_class |= STC.temp | STC.ctfe; + if (!addPatternBinding(variable)) + return setError(); + } + } + else if (arm.recordBindings.length || arm.recordPatternNames.length || arm.hasRestPattern) + { + if (!variant.payloadType) + continue; + auto aliasPayloadType = variant.isTypeAlias && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type : null; + auto recordType = aliasPayloadType && aliasPayloadType.toBasetype().isTypeStruct() + ? aliasPayloadType.toBasetype().isTypeStruct().sym : variant.payloadType; + VarDeclaration[] recordFields; + foreach (field; recordType.fields) + recordFields ~= field; + if (!recordFields.length && recordType.members) + foreach (member; *recordType.members) + if (auto field = member.isVarDeclaration()) + recordFields ~= field; + if (!arm.hasRestPattern && arm.recordBindings.length + arm.recordPatternNames.length != recordFields.length) + { + eSink.error(arm.loc, + "record pattern for variant `%s` must list all fields or use `...`", + variant.ident ? variant.ident.toChars() : "variant"); + return setError(); + } + bool[] usedFields = new bool[](recordFields.length); + foreach (fieldName; arm.recordBindings) + foreach (i, field; recordFields) + if (field.ident == fieldName) + usedFields[i] = true; + foreach (i, fieldName; arm.recordPatternNames) + { + size_t fieldIndex = size_t.max; + foreach (j, field; recordFields) + if (field.ident == fieldName) + { + fieldIndex = j; + break; + } + if (fieldIndex == size_t.max || usedFields[fieldIndex]) + { + eSink.error(arm.loc, "no such or duplicate field `%s` in variant pattern", + fieldName.toChars()); + return setError(); + } + usedFields[fieldIndex] = true; + auto payloadVar = variant.payloadVar; + auto payload = new DotVarExp(arm.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + auto payloadField = variant.isTypeAlias ? variant.payloadType.fields[0] : null; + if (payloadField) + { + payload = new DotVarExp(arm.loc, payload, payloadField); + payload.type = payloadField.type; + } + auto fieldValue = new DotVarExp(arm.loc, payload, recordFields[fieldIndex]); + fieldValue.type = recordFields[fieldIndex].type; + auto patternValue = arm.recordPatterns[i]; + if (auto declaration = patternValue.isDeclarationExp()) + { + auto unpack = declaration.declaration.isUnpackDeclaration(); + if (!unpack || !bindUnpack(unpack, fieldValue)) + return setError(); + continue; + } + auto patternBinding = patternValue.isIdentifierExp(); + if (!patternBinding) + { + eSink.error(patternValue.loc, + "value tests in patterns are not supported; bind the value and use an `if` guard"); + return setError(); + } + auto qualifiedType = exp.condition.type + ? recordFields[fieldIndex].type.addMod(exp.condition.type.mod) + : recordFields[fieldIndex].type; + auto variable = new VarDeclaration(patternBinding.loc, + qualifiedType, patternBinding.ident, null); + variable._init = new ExpInitializer(patternBinding.loc, fieldValue); + variable.storage_class |= STC.temp | STC.ctfe; + if (!addPatternBinding(variable)) + return setError(); + } + if (arm.restBinding) + { + Expressions* restValues = new Expressions(); + auto payloadVar = variant.payloadVar; + auto payload = new DotVarExp(arm.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + auto payloadField = variant.isTypeAlias ? variant.payloadType.fields[0] : null; + if (payloadField) + { + payload = new DotVarExp(arm.loc, payload, payloadField); + payload.type = payloadField.type; + } + foreach (i, field; recordFields) + if (!usedFields[i]) + { + auto fieldValue = new DotVarExp(arm.loc, payload, field); + fieldValue.type = field.type; + restValues.push(fieldValue); + } + auto rest = new VarDeclaration(arm.loc, null, arm.restBinding, + new ExpInitializer(arm.loc, new TupleExp(arm.loc, restValues))); + rest.storage_class |= STC.temp | STC.ctfe; + if (!addPatternBinding(rest)) + return setError(); + } + foreach (bindingName; arm.recordBindings) + { + VarDeclaration field; + foreach (candidate; recordFields) + { + if (candidate.ident == bindingName) + { + field = candidate; + break; + } + } + if (!field) + { + eSink.error(arm.loc, "record pattern field `%s` is not a field of `%s`", + bindingName.toChars(), variant.ident ? variant.ident.toChars() : "variant"); + return setError(); + } + auto qualifiedType = exp.condition.type ? field.type.addMod(exp.condition.type.mod) : field.type; + auto variable = new VarDeclaration(arm.loc, qualifiedType, + bindingName, null); + auto payloadVar = variant.payloadVar; + auto payload = new DotVarExp(arm.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + auto value = new DotVarExp(arm.loc, payload, field); + value.type = qualifiedType; + variable._init = new ExpInitializer(arm.loc, value); + variable.storage_class |= STC.temp | STC.ctfe; + if (!addPatternBinding(variable)) + return setError(); + } + } + break; + } + if (!arm.hasVariant) + { + if (isCallPattern) + eSink.error(arm.loc, "switch expression call pattern `%s` does not name a variant of `%s`", + variantId.toChars(), eu.toPrettyChars()); + else + eSink.error(arm.loc, "switch expression pattern does not match any variant of `%s`", + eu.toPrettyChars()); + return setError(); + } + } + } + else if (!arm.isDefault) + { + eSink.error(arm.loc, "switch expression patterns require an enum union condition"); + return setError(); + } + if (arm.guard) + { + arm.guard = arm.guard.expressionSemantic(armScope); + arm.guard = resolveProperties(armScope, arm.guard); + arm.guard = arm.guard.toBoolean(armScope); + if (arm.guard.op == EXP.error) + return setError(); + } + arm.action = arm.action.expressionSemantic(armScope); + if (arm.action.op == EXP.error) + return setError(); + if (!resultType || arm.action.type.ty == Tnoreturn) + resultType = resultType ? resultType : arm.action.type; + else if (resultType.ty == Tnoreturn) + resultType = arm.action.type; + else if (resultType != arm.action.type) + { + eSink.error(arm.loc, "switch expression arms must have the same type, not `%s` and `%s`", + resultType.toErrMsg(), arm.action.type.toErrMsg()); + return setError(); + } + } + if (!resultType) + { + eSink.error(exp.loc, "switch expression must have at least one arm"); + return setError(); + } + if (auto ts = exp.condition.type.toBasetype().isTypeStruct()) + if (auto eu = ts.sym.isEnumUnionDeclaration()) + if (!checkExhaustivenessAndRedundancy(exp, eu, eSink)) + return setError(); + exp.type = resultType; + Expression defaultAction; + foreach (ref arm; exp.arms) + { + if (arm.isDefault) + defaultAction = arm.action; + } + Expression lowered; + foreach_reverse (ref arm; exp.arms) + { + if (arm.isDefault) + continue; + Expression action = arm.action; + if (arm.hasVariant) + { + auto eu = exp.condition.type.toBasetype().isTypeStruct().sym.isEnumUnionDeclaration(); + auto variant = eu.variants[arm.variantIndex]; + auto tagVar = eu.tagVar; + auto payloadVar = variant.payloadVar; + if (!payloadVar || !payloadVar.type || !payloadVar.type.toBasetype().isTypeStruct()) + { + eSink.error(arm.loc, "unable to resolve switch expression payload"); + return setError(); + } + auto payloadType = payloadVar.type.toBasetype().isTypeStruct().sym; + auto aliasPayloadType = variant.isTypeAlias && variant.payloadType.fields.length + ? variant.payloadType.fields[0].type : null; + auto aliasStruct = aliasPayloadType ? aliasPayloadType.toBasetype().isTypeStruct() : null; + auto recordType = aliasStruct ? aliasStruct.sym : payloadType; + VarDeclaration[] recordFields; + foreach (field; recordType.fields) + recordFields ~= field; + if (!recordFields.length && recordType.members) + foreach (member; *recordType.members) + if (auto field = member.isVarDeclaration()) + recordFields ~= field; + // Bindings must be declared before the guard runs (the guard may + // reference them), so when a guard is present the declarations are + // threaded through the guard instead of the action; the action then + // just reuses the same (already-declared) binding variables. + Expression guardExpr = arm.guard; + foreach_reverse (binding; arm.bindings) + { + if (!binding._init) + { + VarDeclaration field; + foreach (candidate; recordFields) + { + if (candidate.ident == binding.ident) + { + field = candidate; + break; + } + } + if (!field && !aliasStruct && payloadType.members) + { + foreach (member; *payloadType.members) + { + if (auto candidate = member.isVarDeclaration()) + { + if (candidate.ident == binding.ident) + { + field = candidate; + break; + } + } + } + } + if (!field) + { + eSink.error(arm.loc, "unable to resolve switch pattern payload field `%s`", + binding.ident.toChars()); + return setError(); + } + auto payload = new DotVarExp(arm.loc, exp.condition, payloadVar); + payload.type = payloadVar.type; + if (aliasStruct) + { + auto payloadField = payloadType.fields[0]; + payload = new DotVarExp(arm.loc, payload, payloadField); + payload.type = payloadField.type; + } + auto value = new DotVarExp(arm.loc, payload, field); + value.type = field.type; + binding._init = new ExpInitializer(arm.loc, value); + } + auto declaration = new DeclarationExp(arm.loc, binding); + if (guardExpr) + { + guardExpr = new CommaExp(arm.loc, declaration, guardExpr); + guardExpr.type = Type.tbool; + } + else + { + action = new CommaExp(arm.loc, declaration, action); + action.type = arm.action.type; + } + } + auto tag = new DotVarExp(arm.loc, exp.condition, tagVar); + tag.type = tagVar.type; + Expression match = new EqualExp(EXP.equal, arm.loc, tag, + new IntegerExp(arm.loc, arm.variantIndex, Type.tuns8)); + match.type = Type.tbool; + if (guardExpr) + { + match = new LogicalExp(arm.loc, EXP.andAnd, match, guardExpr); + match.type = Type.tbool; + } + if (lowered) + lowered = new CondExp(arm.loc, match, action, lowered); + else if (arm.guard || defaultAction) + { + auto fallback = defaultAction; + if (!fallback) + { + auto assertExp = new AssertExp(arm.loc, new IntegerExp(arm.loc, 0, Type.tint32), + new StringExp(arm.loc, "non-exhaustive pattern match")); + fallback = assertExp.expressionSemantic(sc); + } + lowered = new CondExp(arm.loc, match, action, fallback); + } + else + lowered = action; // last arm of an exhaustive match with no `default` + if (lowered.op == EXP.question) + lowered.type = resultType; + } + } + if (!lowered) + lowered = defaultAction; + if (!lowered) + return setError(); + result = Expression.combine(conditionPrefix, lowered); + } + override void visit(CondExp exp) { static if (LOGSEMANTIC) diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index b7bd774a0b2c..e3c348c52f46 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -1719,8 +1719,118 @@ private final class DsymbolPrettyPrintVisitor : Visitor buf.writenl(); } + void visitEnumUnionDeclaration(EnumUnionDeclaration eu) + { + buf.put("enum union "); + if (!eu.isAnonymous()) + buf.put(eu.toChars()); + buf.writenl(); + buf.put('{'); + buf.writenl(); + buf.level++; + hgs.insideAggregate++; + foreach (variant; eu.variants) + { + if (variant.udas) + { + buf.put("@("); + argsToBuffer(variant.udas, *buf, *hgs); + buf.put(')'); + buf.writenl(); + } + buf.put("case "); + if (variant.ident) + { + buf.put(variant.ident.toString()); + if (variant.isTypeAlias && variant.payload.length) + { + buf.put(" = "); + toCBuffer(variant.payload[0], *buf, null, *hgs); + } + else if (variant.members) + { + buf.writenl(); + buf.put('{'); + buf.writenl(); + buf.level++; + foreach (m; *variant.members) + toCBuffer(m, *buf, *hgs); + buf.level--; + buf.put('}'); + } + else + { + buf.put('('); + foreach (i, p; variant.payload) + { + if (i) buf.put(", "); + toCBuffer(p, *buf, null, *hgs); + if (i < variant.payloadNames.length && variant.payloadNames[i]) + { + buf.put(' '); + buf.put(variant.payloadNames[i].toString()); + } + } + buf.put(')'); + } + } + else if (variant.payload.length) + { + toCBuffer(variant.payload[0], *buf, null, *hgs); + } + buf.put(';'); + buf.writenl(); + } + bool isInternalOrVariantMember(Dsymbol m) + { + if (m is eu.tagVar) + return true; + if (m.isAnonDeclaration()) + return true; + if (auto fd = m.isFuncDeclaration()) + { + if (fd.isGenerated) + return true; + } + if (auto uad = m.isUserAttributeDeclaration()) + { + if (uad.decl && uad.decl.length == 1) + return isInternalOrVariantMember((*uad.decl)[0]); + } + foreach (ref variant; eu.variants) + { + if (m is variant.declaration) + return true; + if (m is variant.payloadType) + return true; + if (m is variant.payloadVar) + return true; + } + return false; + } + + if (eu.members) + { + foreach (m; *eu.members) + { + if (isInternalOrVariantMember(m)) + continue; + toCBuffer(m, *buf, *hgs); + } + } + hgs.insideAggregate--; + buf.level--; + buf.put('}'); + buf.writenl(); + } + void visitStructDeclaration(StructDeclaration d) { + if (auto eu = d.isEnumUnionDeclaration()) + { + visitEnumUnionDeclaration(eu); + return; + } //printf("visitStructDeclaration() %s\n", d.ident.toChars()); buf.put(d.kind()); buf.put(' '); @@ -2056,6 +2166,7 @@ private final class DsymbolPrettyPrintVisitor : Visitor void visit(EnumDeclaration d) { visitEnumDeclaration(d); } void visit(Nspace d) { visitNspace(d); } void visit(StructDeclaration d) { visitStructDeclaration(d); } + void visit(EnumUnionDeclaration d) { visitEnumUnionDeclaration(d); } void visit(ClassDeclaration d) { visitClassDeclaration(d); } void visit(AliasDeclaration d) { visitAliasDeclaration(d); } void visit(AliasAssign d) { visitAliasAssign(d); } @@ -2653,6 +2764,23 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt void visitDeclaration(DeclarationExp e) { + void unpackPatternToBuffer(UnpackDeclaration unpack, bool nested = false) + { + if (nested) + buf.put('('); + foreach (index, declaration; *unpack.decl) + { + if (index) + buf.put(", "); + if (auto variable = declaration.isVarDeclaration()) + buf.put(variable.ident.toString()); + else if (auto child = declaration.isUnpackDeclaration()) + unpackPatternToBuffer(child, true); + } + if (nested) + buf.put(')'); + } + /* Normal dmd execution won't reach here - regular variable declarations * are handled in visit(ExpStatement), so here would be used only when * we'll directly call Expression.toChars() for debugging. @@ -2672,6 +2800,8 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt buf.put(';'); buf.put(')'); } + else if (auto unpack = e.declaration.isUnpackDeclaration()) + unpackPatternToBuffer(unpack); else e.declaration.dsymbolToBuffer(buf, hgs); } } @@ -3097,6 +3227,149 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt expToBuffer(e.e2, PREC.cond, buf, hgs); } + void visitSwitch(SwitchExp e) + { + void staticForeachHeader(StaticForeach sfe) + { + buf.put("static "); + if (auto statement = sfe.aggrfe) + { + buf.put(Token.toString(statement.op)); + buf.put(" ("); + foreach (i, parameter; *statement.parameters) + { + if (i) + buf.put(", "); + if (stcToBuffer(buf, parameter.storageClass)) + buf.put(' '); + if (parameter.type) + typeToBuffer(parameter.type, parameter.ident, buf, hgs); + else + buf.put(parameter.ident.toString()); + } + buf.put("; "); + expToBuffer(statement.aggr, PREC.expr, buf, hgs); + } + else + { + auto statement = sfe.rangefe; + buf.put(Token.toString(statement.op)); + buf.put(" ("); + if (statement.param.type) + typeToBuffer(statement.param.type, statement.param.ident, buf, hgs); + else + buf.put(statement.param.ident.toString()); + buf.put("; "); + expToBuffer(statement.lwr, PREC.expr, buf, hgs); + buf.put(" .. "); + expToBuffer(statement.upr, PREC.expr, buf, hgs); + } + buf.put(")"); + } + + void armsToBuffer(CaseExpArm[] arms) + { + foreach (arm; arms) + { + if (arm.sfe) + { + buf.put(" "); + staticForeachHeader(arm.sfe); + buf.put(" {"); + armsToBuffer(arm.nestedArms); + buf.put(" }"); + continue; + } + if (arm.staticIfCond) + { + buf.put(" "); + conditionToBuffer(arm.staticIfCond, buf, hgs); + buf.put(" {"); + armsToBuffer(arm.nestedArms); + buf.put(" }"); + if (arm.elseArms) + { + buf.put(" else {"); + armsToBuffer(arm.elseArms); + buf.put(" }"); + } + continue; + } + + buf.put(" "); + if (arm.isDefault) + buf.put("default"); + else + { + buf.put("case "); + if (arm.typePattern) + { + typeToBuffer(arm.typePattern, null, buf, hgs); + if (arm.typeBinding) + { + buf.put(" "); + buf.put(arm.typeBinding.toString()); + } + } + else if (arm.pattern) + expToBuffer(arm.pattern, PREC.assign, buf, hgs); + if (arm.recordBindings.length || arm.recordPatternNames.length || arm.hasRestPattern) + { + buf.put(" {"); + bool needsComma; + foreach (binding; arm.recordBindings) + { + if (needsComma) + buf.put(","); + buf.put(" "); + buf.put(binding.toString()); + needsComma = true; + } + foreach (i, name; arm.recordPatternNames) + { + if (needsComma) + buf.put(","); + buf.put(" "); + buf.put(name.toString()); + buf.put(": "); + expToBuffer(arm.recordPatterns[i], PREC.assign, buf, hgs); + needsComma = true; + } + if (arm.hasRestPattern) + { + if (needsComma) + buf.put(","); + buf.put(" "); + if (arm.restBinding) + { + buf.put(arm.restBinding.toString()); + buf.put("..."); + } + else + buf.put("..."); + } + buf.put(" }"); + } + } + if (arm.guard) + { + buf.put(" if ("); + expToBuffer(arm.guard, PREC.expr, buf, hgs); + buf.put(")"); + } + buf.put(" => "); + expToBuffer(arm.action, PREC.assign, buf, hgs); + buf.put(","); + } + } + + buf.put("switch ("); + expToBuffer(e.condition, PREC.expr, buf, hgs); + buf.put(") {"); + armsToBuffer(e.arms); + buf.put(" }"); + } + void visitDefaultInit(DefaultInitExp e) { buf.put(Token.toString(e.tok)); @@ -3192,6 +3465,7 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt case EXP.prePlusPlus: return visitPre(e.isPreExp()); case EXP.remove: return visitRemove(e.isRemoveExp()); case EXP.question: return visitCond(e.isCondExp()); + case EXP.switchExpression: return visitSwitch(e.isSwitchExp()); case EXP.classReference: return visitClassReference(e.isClassReferenceExp()); case EXP.loweredAssignExp: return visitLoweredAssignExp(e.isLoweredAssignExp()); case EXP.construct: return visitConstructExp(e.isConstructExp()); @@ -3941,6 +4215,8 @@ private Expression arrowFuncLiteralResult(FuncLiteralDeclaration f) // to be called if e could be loweredFrom another expression instead of acessing precedence[e.op] directly private PREC expPrecedence(ref HdrGenState hgs, Expression e) { + if (e.op == EXP.switchExpression) + return PREC.assign; if (!hgs.vcg_ast) { if (auto ce = e.isCallExp()) @@ -4722,6 +4998,7 @@ string EXPtoString(EXP op) EXP.assocArrayLiteral : "assocarrayliteral", EXP.classReference : "classreference", EXP.defaultInit : "defaultinit", + EXP.switchExpression : "switchExpression", EXP.typeid_ : "typeid", EXP.is_ : "is", EXP.assert_ : "assert", diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index 1ee041685efd..3df3f30ba578 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -454,11 +454,15 @@ immutable Msgtable[] msgtable = { "classInstanceSize" }, { "classInstanceAlignment" }, { "allMembers" }, + { "allVariants" }, { "derivedMembers" }, { "isSame" }, { "compiles" }, { "getAliasThis" }, { "getAttributes" }, + { "variantTag" }, + { "hasVariant" }, + { "getVariant" }, { "getFunctionAttributes" }, { "getFunctionVariadicStyle" }, { "getParameterStorageClasses" }, @@ -478,6 +482,10 @@ immutable Msgtable[] msgtable = { "isCopyable" }, { "toType" }, { "parameters" }, + { "variantParams" }, + { "variantParamNames" }, + { "variantKind" }, + { "variantDeclarationOf" }, { "needsDestruction" }, // For C++ mangling diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index d1d0e49bfbf3..be516025e73f 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -14,7 +14,6 @@ module dmd.parse; import core.stdc.stdio; -import core.stdc.string; import dmd.astenums; import dmd.errorsink; @@ -46,6 +45,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer CPPMANGLE cppmangle; Loc endloc; // set to location of last right curly int inBrackets; // inside [] of array index or slice + int inEnumUnion; // inside enum union body Loc lookingForElse; // location of lonely if looking for an else bool doUnittests; // parse unittest blocks } @@ -388,20 +388,47 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer switch (token.value) { + case TOK.case_: + if (inEnumUnion > 0) + { + const loc = token.loc; + nextToken(); + AST.EnumUnionVariant variant; + if (token.value == TOK.traits) + { + variant.variantSplice = cast(AST.TraitsExp) parsePrimaryExp(); + if (!variant.variantSplice || variant.variantSplice.ident != Id.variantDeclarationOf) + error(loc, "`__traits(variantDeclarationOf, ...)` expected"); + } + else + { + variant.payload ~= parseType(); + if (!variant.payload.length) + { + error(loc, "enum union variant type expected"); + break; + } + } + check(TOK.semicolon); + s = new AST.EnumUnionCaseDeclaration(loc, variant); + break; + } + goto default; + case TOK.enum_: { /* Determine if this is a manifest constant declaration, * or a conventional enum. */ const tv = peekNext(); - if (tv == TOK.leftCurly || tv == TOK.colon) + if (tv == TOK.union_ || tv == TOK.leftCurly || tv == TOK.colon) s = parseEnum(); else if (tv != TOK.identifier) goto Ldeclaration; else { const nextv = peekNext2(); - if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) + if (nextv == TOK.union_ || nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) s = parseEnum(); else goto Ldeclaration; @@ -1130,9 +1157,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer default: error("declaration expected, not `%s`", token.toChars()); Lerror: - while (token.value != TOK.semicolon && token.value != TOK.endOfFile) + while (token.value != TOK.semicolon && + (!inEnumUnion || token.value != TOK.rightCurly) && + token.value != TOK.endOfFile) + nextToken(); + if (!inEnumUnion || token.value == TOK.semicolon) nextToken(); - nextToken(); s = null; continue; } @@ -1181,7 +1211,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } } - AST.UnpackDeclaration parseUnpackDeclaration(STC g_storage_class, bool parseInitializer = true, bool isParameter = false) + AST.UnpackDeclaration parseUnpackDeclaration(STC g_storage_class, bool parseInitializer = true, + bool isParameter = false, bool isPattern = false) in { assert(token.value == TOK.leftParenthesis); @@ -1201,7 +1232,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Expressions* udas = null; Loc linkloc = this.linkLoc; // (ignored) auto storage_class = g_storage_class; - parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); + if (!isPattern) + parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); /+if (link) error("linkage specification not allowed within unpack declarations");+/ @@ -1210,7 +1242,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (token.value == TOK.leftParenthesis) { // recurse - vars.push(parseUnpackDeclaration(storage_class, false, isParameter)); + vars.push(parseUnpackDeclaration(storage_class, false, isParameter, isPattern)); } else { @@ -1222,7 +1254,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer i = token.ident; nextToken(); } - else + else if (!isPattern) { t = parseBasicType(); t = parseTypeSuffixes(t); @@ -1242,11 +1274,18 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer i = token.ident; nextToken(); } + else + { + error(token.loc, + "value tests in tuple patterns are not supported; bind the value and use an `if` guard"); + parseAssignExp(); + i = Identifier.generateId("__patternError"); + } if (storage_class & STC.autoref) { error("`auto ref` unpacked variables are not supported"); } - if (!t && storage_class == STC.none) + if (!t && storage_class == STC.none && !isPattern) { error("unpacked variable `%s` needs a type or at least one storage class, did you mean `auto %s`?", i.toChars(), i.toChars()); @@ -3327,20 +3366,30 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer /************************************* */ - private AST.EnumDeclaration parseEnum() + private AST.Dsymbol parseEnum() { - AST.EnumDeclaration e; + AST.EnumDeclaration e = null; + AST.EnumUnionDeclaration eu = null; Identifier id; AST.Type memtype; + AST.TemplateParameters* tpl = null; + bool isUnion = false; auto loc = token.loc; // printf("Parser::parseEnum()\n"); nextToken(); + if (token.value == TOK.union_) + { + isUnion = true; + nextToken(); + } id = null; if (token.value == TOK.identifier) { id = token.ident; nextToken(); + if (isUnion && token.value == TOK.leftParenthesis) + tpl = parseTemplateParameterList(); } memtype = null; @@ -3352,12 +3401,166 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer memtype = parseBasicType(); memtype = parseDeclarator(memtype, alt, null); checkCstyleTypeSyntax(typeLoc, memtype, alt, null); + if (isUnion) + error(typeLoc, "enum union declarations cannot have a base type"); } - e = new AST.EnumDeclaration(loc, id, memtype); + if (isUnion) + eu = new AST.EnumUnionDeclaration(loc, id); + else + e = new AST.EnumDeclaration(loc, id, memtype); // opaque type if (token.value == TOK.semicolon && id) nextToken(); + else if (isUnion && token.value == TOK.leftCurly) + { + inEnumUnion++; + scope(exit) inEnumUnion--; + eu.variants = []; + auto memberDecls = new AST.Dsymbols(); + nextToken(); + bool inCaseDeclaration; + while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) + { + const variantLoc = token.loc; + AST.EnumUnionVariant variant; + variant.loc = variantLoc; + + while (token.value == TOK.at) + { + if (STC stc = parseAttribute(variant.udas)) + { + error(variantLoc, "attribute `%s` is not allowed on enum union variants", + token.toChars()); + nextToken(); + } + } + + if (token.value == TOK.case_) + { + nextToken(); + inCaseDeclaration = true; + } + else if (!inCaseDeclaration) + { + AST.Dsymbol lastDecl = cast(AST.Dsymbol) eu; + auto declarations = parseDeclDefs(1, &lastDecl); + if (declarations) + memberDecls.append(declarations); + continue; + } + + if (token.value == TOK.traits) + { + variant.variantSplice = cast(AST.TraitsExp) parsePrimaryExp(); + if (!variant.variantSplice || variant.variantSplice.ident != Id.variantDeclarationOf) + error(variantLoc, "`__traits(variantDeclarationOf, ...)` expected"); + check(TOK.semicolon); + eu.variants ~= variant; + inCaseDeclaration = false; + continue; + } + else if (token.value == TOK.identifier) + { + if (peekNext() == TOK.assign) + { + variant.ident = token.ident; + variant.isTypeAlias = true; + nextToken(); + nextToken(); + if (token.value == TOK.identifier && token.ident is variant.ident && + (peekNext() == TOK.comma || peekNext() == TOK.semicolon || + peekNext() == TOK.rightCurly)) + { + error(variantLoc, "`case %s = %s` cannot alias itself, use a qualified name", + variant.ident.toChars(), token.ident.toChars()); + } + variant.payload ~= parseType(); + if (!variant.payload.length) + { + error(variantLoc, "enum union variant type expected"); + break; + } + } + else + { + if (peekNext() != TOK.leftParenthesis && peekNext() != TOK.leftCurly) + variant.payload ~= parseType(); + else + { + variant.ident = token.ident; + nextToken(); + if (token.value == TOK.leftParenthesis) + { + nextToken(); + while (token.value != TOK.rightParenthesis && token.value != TOK.endOfFile) + { + Identifier payloadIdent; + variant.payload ~= parseType(&payloadIdent); + variant.payloadNames ~= payloadIdent; + if (token.value != TOK.comma) + break; + nextToken(); + } + check(TOK.rightParenthesis); + } + else if (token.value == TOK.leftCurly) + { + nextToken(); + variant.members = parseDeclDefs(0); + check(TOK.rightCurly); + } + } + } + } + else + { + variant.payload ~= parseType(); + if (!variant.payload.length) + { + error(variantLoc, "enum union variant name expected"); + break; + } + } + + eu.variants ~= variant; + if (token.value == TOK.comma) + { + nextToken(); + if (token.value == TOK.case_) + inCaseDeclaration = false; + } + else if (token.value == TOK.semicolon) + { + nextToken(); + inCaseDeclaration = false; + } + else if (token.value != TOK.rightCurly) + { + error(token.loc, "`,` or `;` expected after enum union variant"); + inCaseDeclaration = false; + } + } + check(TOK.rightCurly); + + if (eu.variants.length > 256) + error(loc, "enum union cannot have more than 256 variants"); + + eu.tagVar = new AST.VarDeclaration(loc, AST.Type.tuns8, Id.__tag, null); + eu.payloadUnion = new AST.UnionDeclaration(loc, null); + eu.payloadUnion.members = new AST.Dsymbols(); + foreach (variant; eu.variants) + { + foreach (i, payload; variant.payload) + eu.payloadUnion.members.push(new AST.VarDeclaration( + loc, payload, Identifier.generateId("__enumPayload"), null)); + } + eu.members = new AST.Dsymbols(); + eu.members.push(eu.tagVar); + eu.members.push(new AST.AnonDeclaration(loc, true, eu.payloadUnion.members)); + if (memberDecls) + eu.members.append(memberDecls); + } else if (token.value == TOK.leftCurly) { bool isAnonymousEnum = !id; @@ -3528,7 +3731,13 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("expected `{`, not `%s` for enum declaration", token.toChars()); } //printf("-parseEnum() %s\n", e.toChars()); - return e; + if (tpl) + { + auto members = new AST.Dsymbols(); + members.push(isUnion ? cast(AST.Dsymbol) eu : cast(AST.Dsymbol) e); + return new AST.TemplateDeclaration(loc, id, tpl, null, members); + } + return isUnion ? cast(AST.Dsymbol) eu : cast(AST.Dsymbol) e; } /******************************** @@ -4614,12 +4823,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.enum_: { const tv = peekNext(); - if (tv == TOK.leftCurly || tv == TOK.colon) + if (tv == TOK.union_ || tv == TOK.leftCurly || tv == TOK.colon) break; if (tv == TOK.identifier) { const nextv = peekNext2(); - if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) + if (nextv == TOK.union_ || nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) break; } stc = STC.manifest; @@ -4751,6 +4960,27 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer auto a = new AST.Dsymbols(); a.push(d); + if (d.isEnumUnionDeclaration()) + { + if (storage_class) + { + d = new AST.StorageClassDeclaration(storage_class, a); + a = new AST.Dsymbols(); + a.push(d); + } + if (setAlignment) + { + d = new AST.AlignDeclaration(d.loc, ealign, a); + a = new AST.Dsymbols(); + a.push(d); + } + if (link != linkage) + { + d = new AST.LinkDeclaration(linkloc, link, a); + a = new AST.Dsymbols(); + a.push(d); + } + } if (udas) { d = new AST.UserAttributeDeclaration(udas, a); @@ -5864,7 +6094,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ private Foreach parseForeach(alias Foreach)(Loc loc, AST.Dsymbol* pLastDecl) { - static if (is(Foreach == AST.StaticForeachStatement) || is(Foreach == AST.StaticForeachDeclaration)) + static if (is(Foreach == AST.StaticForeachStatement) || is(Foreach == AST.StaticForeachDeclaration) || is(Foreach == AST.StaticForeach)) { nextToken(); } @@ -6025,6 +6255,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { return new AST.StaticForeachStatement(loc, new AST.StaticForeach(loc, null, rangefe)); } + else static if (is(Foreach == AST.StaticForeach)) + { + return new AST.StaticForeach(loc, null, rangefe); + } } else { @@ -6051,6 +6285,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { return new AST.StaticForeachStatement(loc, new AST.StaticForeach(loc, aggrfe, null)); } + else static if (is(Foreach == AST.StaticForeach)) + { + return new AST.StaticForeach(loc, aggrfe, null); + } } } @@ -6305,7 +6543,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * we check if the next token is a semicolon and simply output the error, * otherwise we fall back on the old path (advancing the token). */ - if (token.value != TOK.semicolon && peek(&token).value == TOK.semicolon) + const isSwitchExpr = exp.isSwitchExp() || (exp.isCastExp() && exp.isCastExp().e1.isSwitchExp()); + if (token.value != TOK.semicolon && + (peek(&token).value == TOK.semicolon || (token.value == TOK.rightCurly && isSwitchExpr))) error("found `%s` when expecting `;` following expression", token.toChars()); else { @@ -6451,7 +6691,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer */ AST.Dsymbol d; const tv = peekNext(); - if (tv == TOK.leftCurly || tv == TOK.colon) + if (tv == TOK.union_ || tv == TOK.leftCurly || tv == TOK.colon) d = parseEnum(); else if (tv != TOK.identifier) goto Ldeclaration; @@ -6771,8 +7011,39 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer break; } case TOK.switch_: - isfinal = false; - goto Lswitch; + { + auto afterCondition = peekPastParen(peek(&token)); + if (afterCondition.value == TOK.leftCurly) + { + size_t nesting; + for (auto lookahead = peek(afterCondition); lookahead.value != TOK.endOfFile; + lookahead = peek(lookahead)) + { + if (lookahead.value == TOK.leftParenthesis || lookahead.value == TOK.leftBracket || + lookahead.value == TOK.leftCurly) + ++nesting; + else if (lookahead.value == TOK.rightParenthesis || lookahead.value == TOK.rightBracket || + lookahead.value == TOK.rightCurly) + { + if (!nesting) + break; + --nesting; + } + else if (!nesting && lookahead.value == TOK.goesTo) + { + auto exp = parsePrimaryExp(); + s = new AST.ExpStatement(loc, exp); + break; + } + else if (!nesting && lookahead.value == TOK.colon) + break; + } + } + if (s) + break; + isfinal = false; + goto Lswitch; + } Lswitch: { @@ -8450,6 +8721,304 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return e; } + private bool parseSwitchExpArm(ref AST.CaseExpArm[] arms, ref bool hasDefault) + { + auto armLoc = token.loc; + if (token.value == TOK.static_) + { + if (peekNext() == TOK.foreach_) + { + auto sfeLoc = token.loc; + auto sfe = parseForeach!(AST.StaticForeach)(sfeLoc, null); + if (!sfe) + return false; + auto arm = AST.CaseExpArm(); + arm.loc = sfeLoc; + arm.sfe = sfe; + if (token.value == TOK.leftCurly) + { + nextToken(); + while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) + { + if (!parseSwitchExpArm(arm.nestedArms, hasDefault)) + return false; + } + check(TOK.rightCurly); + } + else + { + if (!parseSwitchExpArm(arm.nestedArms, hasDefault)) + return false; + } + arms ~= arm; + if (token.value == TOK.comma || token.value == TOK.semicolon) + nextToken(); + return true; + } + else if (peekNext() == TOK.if_) + { + auto sifLoc = token.loc; + auto cond = parseStaticIfCondition(); + if (!cond) + return false; + auto arm = AST.CaseExpArm(); + arm.loc = sifLoc; + arm.staticIfCond = cond; + if (token.value == TOK.leftCurly) + { + nextToken(); + while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) + { + if (!parseSwitchExpArm(arm.nestedArms, hasDefault)) + return false; + } + check(TOK.rightCurly); + } + else + { + if (!parseSwitchExpArm(arm.nestedArms, hasDefault)) + return false; + } + if (token.value == TOK.else_) + { + nextToken(); + if (token.value == TOK.leftCurly) + { + nextToken(); + while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) + { + if (!parseSwitchExpArm(arm.elseArms, hasDefault)) + return false; + } + check(TOK.rightCurly); + } + else + { + if (!parseSwitchExpArm(arm.elseArms, hasDefault)) + return false; + } + } + arms ~= arm; + if (token.value == TOK.comma || token.value == TOK.semicolon) + nextToken(); + return true; + } + else + { + error(armLoc, "`static foreach` or `static if` expected in switch expression"); + return false; + } + } + + AST.Expression pattern; + AST.Type typePattern; + Identifier typeBinding; + Identifier[] recordBindings; + bool hasRestPattern; + Identifier[] recordPatternNames; + AST.Expression[] recordPatterns; + Identifier restBinding; + bool isDefault; + if (token.value == TOK.case_) + { + nextToken(); + Token* patternEnd = &token; + Token* qualifiedEnd = &token; + while (qualifiedEnd.value == TOK.identifier && peek(qualifiedEnd).value == TOK.dot && + peek(peek(qualifiedEnd)).value == TOK.identifier) + qualifiedEnd = peek(peek(qualifiedEnd)); + const isQualifiedTypeBinding = qualifiedEnd !is &token && + peek(qualifiedEnd).value == TOK.identifier; + if ((token.value != TOK.identifier && isBasicType(&patternEnd)) || + (token.value == TOK.identifier && + (peekNext() == TOK.identifier || peekNext() == TOK.not || isQualifiedTypeBinding))) + { + typePattern = parseType(&typeBinding); + } + else if (token.value == TOK.identifier) + { + auto patternLoc = token.loc; + pattern = new AST.IdentifierExp(patternLoc, token.ident); + nextToken(); + while (token.value == TOK.dot) + { + const dotLoc = token.loc; + nextToken(); + if (token.value != TOK.identifier) + { + error(token.loc, "identifier expected following `.` in switch expression pattern"); + break; + } + pattern = new AST.DotIdExp(dotLoc, pattern, token.ident); + nextToken(); + } + if (token.value == TOK.leftCurly) + { + nextToken(); + while (1) + { + if (token.value == TOK.rightCurly) + { + nextToken(); + break; + } + if (token.value == TOK.identifier) + { + auto fieldName = token.ident; + if (peekNext() == TOK.colon) + { + nextToken(); + nextToken(); + recordPatternNames ~= fieldName; + if (token.value == TOK.leftParenthesis) + { + auto unpack = parseUnpackDeclaration( + STC.auto_ | STC.temp | STC.ctfe, false, false, true); + recordPatterns ~= new AST.DeclarationExp(unpack.loc, unpack); + } + else + recordPatterns ~= parseAssignExp(); + } + else if (peekNext() == TOK.dotDotDot) + { + restBinding = fieldName; + hasRestPattern = true; + nextToken(); + nextToken(); + } + else + { + recordBindings ~= fieldName; + nextToken(); + } + } + else if (token.value == TOK.dotDotDot) + { + hasRestPattern = true; + nextToken(); + } + else if (token.value == TOK.comma) + { + nextToken(); + } + else + { + break; + } + } + } + } + else + pattern = parsePrimaryExp(); + while (token.value == TOK.leftParenthesis) + { + auto args = new AST.Expressions(); + auto names = new AST.ArgumentLabels(); + if (peekNext() == TOK.dotDotDot) + { + nextToken(); + nextToken(); + check(TOK.rightParenthesis); + hasRestPattern = true; + } + else if (peekNext() == TOK.identifier && peekNext2() == TOK.dotDotDot) + { + nextToken(); + restBinding = token.ident; + nextToken(); + nextToken(); + check(TOK.rightParenthesis); + hasRestPattern = true; + } + else + { + nextToken(); + while (token.value != TOK.rightParenthesis && token.value != TOK.endOfFile) + { + if (peekNext() == TOK.colon) + { + const labelLoc = token.loc; + auto label = token.ident; + check(TOK.identifier); + check(TOK.colon); + names.push(ArgumentLabel(label, labelLoc)); + } + else + names.push(ArgumentLabel(null, Loc.init)); + + AST.Expression argument; + if (token.value == TOK.leftParenthesis) + { + auto unpack = parseUnpackDeclaration( + STC.auto_ | STC.temp | STC.ctfe, false, false, true); + argument = new AST.DeclarationExp(unpack.loc, unpack); + } + else + argument = parseAssignExp(); + args.push(argument); + if (token.value != TOK.comma) + break; + nextToken(); + } + check(TOK.rightParenthesis); + } + pattern = new AST.CallExp(pattern.loc, pattern, args, names); + } + } + else if (token.value == TOK.default_) + { + hasDefault = true; + isDefault = true; + nextToken(); + } + else + { + error(armLoc, "`case`, `default`, `static foreach`, or `static if` expected in switch expression"); + return false; + } + AST.Expression guard; + if (token.value == TOK.if_) + { + if (isDefault) + error(token.loc, "`default` arm cannot have an `if` guard"); + nextToken(); + check(TOK.leftParenthesis); + guard = parseExpression(); + check(TOK.rightParenthesis); + } + if (token.value != TOK.goesTo) + { + error(token.loc, "`=>` expected in switch expression arm"); + return false; + } + nextToken(); + auto action = parseAssignExp(); + if (typePattern) + pattern = new AST.TypeExp(armLoc, typePattern); + auto arm = AST.CaseExpArm(); + arm.loc = armLoc; + arm.pattern = pattern; + arm.typePattern = typePattern; + arm.typeBinding = typeBinding; + arm.recordBindings = recordBindings; + arm.hasRestPattern = hasRestPattern; + arm.recordPatternNames = recordPatternNames; + arm.recordPatterns = recordPatterns; + arm.restBinding = restBinding; + arm.guard = guard; + arm.isDefault = isDefault; + arm.action = action; + arms ~= arm; + if (token.value == TOK.comma) + nextToken(); + else if (token.value != TOK.rightCurly) + { + error(token.loc, "`,` or `}` expected after switch expression arm"); + return false; + } + return true; + } + /********************************* Expression Parser ***************************/ AST.Expression parsePrimaryExp() @@ -8462,6 +9031,43 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer //printf("parsePrimaryExp(): loc = %d\n", loc.linnum); switch (token.value) { + case TOK.switch_: + { + nextToken(); + check(TOK.leftParenthesis); + auto condition = parseExpression(); + check(TOK.rightParenthesis); + check(TOK.leftCurly); + + AST.CaseExpArm[] arms; + bool hasDefault; + while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) + { + if (!parseSwitchExpArm(arms, hasDefault)) + { + int curlies = 1; + while (token.value != TOK.endOfFile) + { + if (token.value == TOK.leftCurly) + curlies++; + else if (token.value == TOK.rightCurly) + { + curlies--; + if (curlies == 0) + { + nextToken(); + break; + } + } + nextToken(); + } + goto Lerr; + } + } + check(TOK.rightCurly); + e = new AST.SwitchExp(loc, condition, arms, hasDefault); + break; + } case TOK.identifier: { if (peekNext() == TOK.arrow) @@ -8855,7 +9461,13 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { tok = token.value; nextToken(); - if (tok == TOK.equal && (token.value == TOK.struct_ || token.value == TOK.union_ + if (tok == TOK.equal && token.value == TOK.enum_ && peekNext() == TOK.union_) + { + tok2 = TOK.enumUnion; + nextToken(); + nextToken(); + } + else if (tok == TOK.equal && (token.value == TOK.struct_ || token.value == TOK.union_ || token.value == TOK.class_ || token.value == TOK.super_ || token.value == TOK.enum_ || token.value == TOK.interface_ || token.value == TOK.package_ || token.value == TOK.module_ || token.value == TOK.argumentTypes || token.value == TOK.parameters diff --git a/compiler/src/dmd/statementsem.d b/compiler/src/dmd/statementsem.d index c6faf6b62f65..669e770a11b9 100644 --- a/compiler/src/dmd/statementsem.d +++ b/compiler/src/dmd/statementsem.d @@ -242,11 +242,44 @@ Statement statementSemanticVisit(Statement s, Scope* sc) result = s; return; } + + bool hasSwitchExpressionSideEffect(Expression exp) + { + if (auto call = exp.isCallExp()) + if (call.f && call.f.isGenerated && call.f.parent && call.f.parent.isEnumUnionDeclaration()) + { + if (call.arguments) + foreach (argument; *call.arguments) + if (hasSwitchExpressionSideEffect(argument)) + return true; + return false; + } + return hasSideEffect(exp); + } + + bool hasBindingInitializerSideEffect(Expression exp) + { + if (auto comma = exp.isCommaExp()) + return hasBindingInitializerSideEffect(comma.e1) || + hasBindingInitializerSideEffect(comma.e2); + if (auto declaration = exp.isDeclarationExp()) + { + if (auto variable = declaration.declaration.isVarDeclaration()) + if (auto initializer = variable._init ? variable._init.isExpInitializer() : null) + return hasBindingInitializerSideEffect(initializer.exp); + return false; + } + if (auto construct = exp.isConstructExp()) + return hasBindingInitializerSideEffect(construct.e2); + return hasSwitchExpressionSideEffect(exp); + } //printf("ExpStatement::semantic() %s\n", exp.toChars()); // Allow CommaExp in ExpStatement because return isn't used CommaExp.allow(s.exp); + auto switchExp = s.exp.isSwitchExp(); + auto switchCondition = switchExp ? switchExp.condition : null; s.exp = s.exp.expressionSemantic(sc); s.exp = resolveProperties(sc, s.exp); s.exp = s.exp.addDtorHook(sc); @@ -259,8 +292,29 @@ Statement statementSemanticVisit(Statement s, Scope* sc) } if (checkMustUse(s.exp, sc)) s.exp = ErrorExp.get(); - if (!sc.inCfile && discardValue(s.exp)) - s.exp = ErrorExp.get(); + if (!sc.inCfile) + { + if (switchExp) + { + bool hasEffect = hasSwitchExpressionSideEffect(switchCondition); + foreach (arm; switchExp.arms) + { + hasEffect = hasEffect || hasSideEffect(arm.action) || + (arm.guard && hasSideEffect(arm.guard)); + foreach (binding; arm.bindings) + if (auto initializer = binding._init ? binding._init.isExpInitializer() : null) + hasEffect = hasEffect || hasBindingInitializerSideEffect(initializer.exp); + } + if (!hasEffect) + { + eSink.error(switchExp.loc, + "switch expression has no effect; use `cast(void)` to discard its value"); + s.exp = ErrorExp.get(); + } + } + else if (discardValue(s.exp)) + s.exp = ErrorExp.get(); + } s.exp = s.exp.optimize(WANTvalue); s.exp = s.exp.checkGC(sc); @@ -1229,7 +1283,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc) */ auto id = Identifier.generateId("__r"); auto ie = new ExpInitializer(loc, new SliceExp(loc, fs.aggr, null, null)); - const valueIsRef = (*fs.parameters)[$ - 1].isReference(); + const valueIsRef = fs.value.isReference(); VarDeclaration tmp; if (fs.aggr.isArrayLiteralExp() && !valueIsRef) { @@ -4693,7 +4747,9 @@ public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachState { if (isStatic || tb.ty == Tfunction || storageClass & STC.alias_) { - if (auto ve = e.isVarExp()) + if (auto de = e.isDsymbolExp()) + ds = de.s; + else if (auto ve = e.isVarExp()) ds = ve.var; else if (auto dve = e.isDotVarExp()) ds = dve.var; diff --git a/compiler/src/dmd/templatesem.d b/compiler/src/dmd/templatesem.d index 1ce7ae1e9b0b..f0ed02bc5986 100644 --- a/compiler/src/dmd/templatesem.d +++ b/compiler/src/dmd/templatesem.d @@ -5550,7 +5550,15 @@ bool TemplateInstance_semanticTiargs(Loc loc, Scope* sc, Objects* tiargs, int fl ea = ea.expressionSemantic(sc); sc = sc.endCTFE(); - if (auto varExp = ea.isVarExp()) + if (auto symbolExp = ea.isDsymbolExp()) + { + if (symbolExp.preserveSymbol) + { + sa = symbolExp.s; + goto Ldsym; + } + } + else if (auto varExp = ea.isVarExp()) { /* If the parameter is a function that is not called * explicitly, i.e. `foo!func` as opposed to `foo!func()`, diff --git a/compiler/src/dmd/tokens.d b/compiler/src/dmd/tokens.d index eff7c68681fe..cf42d2c85159 100644 --- a/compiler/src/dmd/tokens.d +++ b/compiler/src/dmd/tokens.d @@ -164,6 +164,7 @@ enum TOK : ubyte interface_, union_, enum_, + enumUnion, import_, alias_, override_, @@ -377,6 +378,7 @@ enum EXP : ubyte dot, comma, question, + switchExpression, andAnd, orOr, prePlusPlus, @@ -739,6 +741,7 @@ extern (C++) struct Token TOK.interface_: "interface", TOK.union_: "union", TOK.enum_: "enum", + TOK.enumUnion: "enum union", TOK.import_: "import", TOK.mixin_: "mixin", TOK.static_: "static", diff --git a/compiler/src/dmd/traits.d b/compiler/src/dmd/traits.d index 64e628d45776..0e76b3867e3e 100644 --- a/compiler/src/dmd/traits.d +++ b/compiler/src/dmd/traits.d @@ -26,6 +26,7 @@ import dmd.declaration; import dmd.dinterpret; import dmd.dmodule; import dmd.dscope; +import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; @@ -81,6 +82,8 @@ private Dsymbol getDsymbolWithoutExpCtx(RootObject oarg) { if (auto e = isExpression(oarg)) { + if (auto de = e.isDsymbolExp()) + return de.s; if (auto dve = e.isDotVarExp()) return dve.var; if (auto dte = e.isDotTemplateExp()) @@ -328,10 +331,18 @@ Expression semanticTraits(TraitsExp e, Scope* sc) } auto eSink = global.errorSink; + auto rawParentArgument = e.ident == Id.parent && e.args && e.args.length == 1 + ? (*e.args)[0] : null; if (e.ident != Id.compiles && e.ident != Id.isSame && e.ident != Id.identifier && + e.ident != Id.variantKind && + e.ident != Id.variantParams && + e.ident != Id.variantParamNames && + e.ident != Id.hasVariant && + e.ident != Id.getVariant && + e.ident != Id.variantTag && e.ident != Id.getProtection && e.ident != Id.getVisibility && e.ident != Id.getAttributes) { @@ -373,6 +384,143 @@ Expression semanticTraits(TraitsExp e, Scope* sc) return IntegerExp.createBool(false); } + EnumUnionDeclaration enumUnionType(RootObject object) + { + auto type = getType(object); + auto structType = type ? type.toBasetype().isTypeStruct() : null; + return structType ? structType.sym.isEnumUnionDeclaration() : null; + } + + Type enumUnionPayloadType(ref EnumUnionVariant variant) + { + if (variant.payloadType && variant.payloadType.fields.length) + return variant.payloadType.fields[0].type; + return variant.payload.length == 1 ? variant.payload[0] : null; + } + + bool enumUnionVariant(RootObject object, out EnumUnionDeclaration eu, + out size_t index) + { + Dsymbol variantSymbol(RootObject candidate) + { + auto symbol = getDsymbolWithoutExpCtx(candidate); + if (!symbol) + { + auto type = getType(candidate); + if (auto identifierType = type ? type.isTypeIdentifier() : null) + { + Dsymbol scopeSymbol; + auto base = sc.search(identifierType.loc, identifierType.ident, + scopeSymbol); + if (identifierType.idents.length == 1 && base) + { + if (auto aliasDeclaration = base.isAliasDeclaration()) + { + aliasDeclaration.dsymbolSemantic(sc); + base = aliasDeclaration.toAlias(); + } + if (auto tuple = base.isTupleDeclaration()) + { + if (auto indexExpression = isExpression(identifierType.idents[0])) + { + auto indexValue = indexExpression.expressionSemantic(sc) + .ctfeInterpret(); + if (indexValue.op != EXP.error && indexValue.type && + indexValue.type.isIntegral() && + indexValue.toUInteger() < tuple.objects.length) + symbol = variantSymbol((*tuple.objects)[ + cast(size_t) indexValue.toUInteger()]); + } + } + } + if (!symbol) + symbol = identifierType.idents.length + ? identifierType.toDsymbol(sc) : base; + } + auto expression = isExpression(candidate); + if (!symbol && (expression ? expression.isIdentifierExp() : null)) + { + auto identifier = expression.isIdentifierExp(); + Dsymbol scopeSymbol; + symbol = sc.search(identifier.loc, identifier.ident, scopeSymbol); + } + else if (!symbol && (expression ? expression.isTraitsExp() : null)) + { + auto trait = expression.isTraitsExp(); + if (trait.ident == Id.getVariant) + symbol = getDsymbolWithoutExpCtx(trait.expressionSemantic(sc)); + } + else if (!symbol && (expression ? expression.isIndexExp() : null)) + { + auto indexExpression = expression.isIndexExp(); + auto aggregate = variantSymbol(indexExpression.e1); + if (auto aliasDeclaration = aggregate + ? aggregate.isAliasDeclaration() : null) + { + aliasDeclaration.dsymbolSemantic(sc); + aggregate = aliasDeclaration.toAlias(); + } + if (auto tuple = aggregate ? aggregate.isTupleDeclaration() : null) + { + auto indexValue = indexExpression.e2.expressionSemantic(sc).ctfeInterpret(); + if (indexValue.op != EXP.error && indexValue.type && + indexValue.type.isIntegral() && + indexValue.toUInteger() < tuple.objects.length) + symbol = variantSymbol((*tuple.objects)[ + cast(size_t) indexValue.toUInteger()]); + } + } + } + return symbol; + } + + auto symbol = variantSymbol(object); + while (symbol) + { + auto aliasDeclaration = symbol.isAliasDeclaration(); + if (!aliasDeclaration) + break; + if (!aliasDeclaration.aliassym) + break; + symbol = aliasDeclaration.aliassym; + } + if (auto descriptor = symbol ? symbol.isEnumUnionCaseDeclaration() : null) + { + eu = descriptor.parent ? descriptor.parent.isEnumUnionDeclaration() : null; + if (eu) + foreach (variantIndex, ref variant; eu.variants) + if (variant.descriptor is descriptor) + { + index = variantIndex; + return true; + } + } + if (symbol) + { + auto parent = symbol.toParent2(); + eu = parent ? parent.isEnumUnionDeclaration() : null; + if (eu) + foreach (variantIndex, ref variant; eu.variants) + if (variant.declaration is symbol) + { + index = variantIndex; + return true; + } + } + eu = null; + return false; + } + + Expression enumUnionVariantObject(EnumUnionDeclaration eu, ref EnumUnionVariant variant) + { + if (!variant.descriptor) + return null; + auto result = new DsymbolExp(e.loc, variant.descriptor, false); + result.type = variant.descriptor.type; + result.preserveSymbol = true; + return result; + } + /******** * Gets the function type from a given AST node * if the node is a function of some sort. @@ -788,8 +936,217 @@ Expression semanticTraits(TraitsExp e, Scope* sc) return cd.com ? True() : False(); return False(); } + if (e.ident == Id.allVariants) + { + if (dim != 1) + return dimError(1); + + auto eu = enumUnionType((*e.args)[0]); + if (!eu) + { + eSink.error(e.loc, "argument `%s` is not an enum union type", (*e.args)[0].toErrMsg()); + return ErrorExp.get(); + } + + auto exps = new Expressions(); + exps.reserve(eu.variants.length); + foreach (ref variant; eu.variants) + { + auto variantObject = enumUnionVariantObject(eu, variant); + if (!variantObject) + { + eSink.error(e.loc, "unable to resolve variant of enum union `%s`", + eu.toPrettyChars()); + return ErrorExp.get(); + } + exps.push(variantObject); + } + return (new TupleExp(e.loc, exps)).expressionSemantic(sc); + } + if (e.ident == Id.hasVariant || e.ident == Id.getVariant) + { + if (!TemplateInstance_semanticTiargs(e.loc, sc, e.args, 0)) + return e.ident == Id.hasVariant ? False() : ErrorExp.get(); + if (dim != 2) + return dimError(2); + + auto eu = enumUnionType((*e.args)[0]); + if (!eu) + { + if (e.ident == Id.hasVariant) + return False(); + eSink.error(e.loc, "first argument `%s` is not an enum union type", + (*e.args)[0].toErrMsg()); + return ErrorExp.get(); + } + + auto key = (*e.args)[1]; + auto keyExpression = isExpression(key); + auto keyString = keyExpression ? keyExpression.toStringExp() : null; + auto keyType = getType(key); + foreach (ref variant; eu.variants) + { + bool matches; + if (keyString) + { + auto utf8 = keyString.toUTF8(sc); + matches = variant.ident && variant.ident.toString() == utf8.peekString(); + } + else if (keyType && !variant.ident) + { + auto payloadType = enumUnionPayloadType(variant); + matches = payloadType && payloadType.toBasetype().equals(keyType.toBasetype()); + } + if (!matches) + continue; + + if (e.ident == Id.hasVariant) + return True(); + auto result = enumUnionVariantObject(eu, variant); + if (result) + return result.expressionSemantic(sc); + break; + } + + if (e.ident == Id.hasVariant) + return False(); + eSink.error(e.loc, "variant `%s` does not exist in enum union `%s`", + key.toErrMsg(), eu.toPrettyChars()); + return ErrorExp.get(); + } + if (e.ident == Id.variantTag) + { + if (dim != 1) + return dimError(1); + + EnumUnionDeclaration eu; + size_t index; + if (!enumUnionVariant((*e.args)[0], eu, index) && + (!TemplateInstance_semanticTiargs(e.loc, sc, e.args, 0) || + !enumUnionVariant((*e.args)[0], eu, index))) + { + eSink.error(e.loc, "argument `%s` is not an enum union variant", + (*e.args)[0].toErrMsg()); + return ErrorExp.get(); + } + else + return new IntegerExp(e.loc, index, eu.tagVar.type); + } + if (e.ident == Id.variantKind) + { + if (dim != 1) + return dimError(1); + + auto object = (*e.args)[0]; + EnumUnionDeclaration eu; + size_t index; + if (!enumUnionVariant(object, eu, index)) + { + if (!TemplateInstance_semanticTiargs(e.loc, sc, e.args, 0)) + return ErrorExp.get(); + object = (*e.args)[0]; + if (!enumUnionVariant(object, eu, index)) + { + eSink.error(e.loc, "argument `%s` is not an enum union variant", object.toErrMsg()); + return ErrorExp.get(); + } + } + auto variant = &eu.variants[index]; + const kind = !variant.ident ? "bare" + : variant.isTypeAlias ? "alias" + : variant.members ? "struct" + : variant.payload.length ? "tuple" : "unit"; + return (new StringExp(e.loc, kind)).expressionSemantic(sc); + } + if (e.ident == Id.variantParams) + { + if (dim != 1) + return dimError(1); + + auto object = (*e.args)[0]; + EnumUnionDeclaration eu; + size_t variantIndex; + if (!enumUnionVariant(object, eu, variantIndex)) + { + if (!TemplateInstance_semanticTiargs(e.loc, sc, e.args, 0)) + return ErrorExp.get(); + object = (*e.args)[0]; + if (!enumUnionVariant(object, eu, variantIndex)) + { + eSink.error(e.loc, "argument `%s` is not an enum union variant", object.toErrMsg()); + return ErrorExp.get(); + } + } + auto parameters = new Parameters(); + auto variant = &eu.variants[variantIndex]; + if (variant.ident && !variant.isTypeAlias) + { + if (variant.members) + foreach (field; variant.payloadType.fields) + parameters.push(new Parameter(e.loc, STC.none, field.type, + null, null, null, null)); + else + foreach (payload; variant.payload) + parameters.push(new Parameter(e.loc, STC.none, payload, + null, null, null, null)); + } + return (new TypeExp(e.loc, new TypeTuple(parameters))).expressionSemantic(sc); + } + if (e.ident == Id.variantParamNames) + { + if (dim != 1) + return dimError(1); + + auto object = (*e.args)[0]; + EnumUnionDeclaration eu; + size_t variantIndex; + if (!enumUnionVariant(object, eu, variantIndex)) + { + if (!TemplateInstance_semanticTiargs(e.loc, sc, e.args, 0)) + return ErrorExp.get(); + object = (*e.args)[0]; + if (!enumUnionVariant(object, eu, variantIndex)) + { + eSink.error(e.loc, "argument `%s` is not an enum union variant", object.toErrMsg()); + return ErrorExp.get(); + } + } + auto names = new Expressions(); + auto variant = &eu.variants[variantIndex]; + if (variant.ident && !variant.isTypeAlias) + { + auto parameterCount = variant.members + ? variant.payloadType.fields.length : variant.payload.length; + foreach (parameterIndex; 0 .. parameterCount) + { + auto ident = variant.members + ? variant.payloadType.fields[parameterIndex].ident + : parameterIndex < variant.payloadNames.length + ? variant.payloadNames[parameterIndex] : null; + names.push(new StringExp(e.loc, ident ? ident.toString() : "")); + } + } + return (new TupleExp(e.loc, names)).expressionSemantic(sc); + } + if (e.ident == Id.variantDeclarationOf) + { + eSink.error(e.loc, + "`__traits(variantDeclarationOf)` is only valid in an enum union case declaration"); + return ErrorExp.get(); + } if (e.ident == Id.identifier) { + if (dim == 1) + { + EnumUnionDeclaration variantEnum; + size_t variantIndex; + if (enumUnionVariant((*e.args)[0], variantEnum, variantIndex)) + { + auto ident = variantEnum.variants[variantIndex].descriptor.ident; + return (new StringExp(e.loc, + ident ? ident.toString() : "")).expressionSemantic(sc); + } + } // Get identifier for symbol as a string literal /* Specify 0 for bit 0 of the flags argument to semanticTiargs() so that * a symbol should not be folded to a constant. @@ -814,8 +1171,13 @@ Expression semanticTraits(TraitsExp e, Scope* sc) else { Dsymbol s = getDsymbolWithoutExpCtx(o); + if (auto variant = s ? s.isEnumUnionCaseDeclaration() : null) + return (new StringExp(e.loc, + variant.ident ? variant.ident.toString() : "")).expressionSemantic(sc); if (!s || !s.ident) { + if (isType(o)) + return (new StringExp(e.loc, "")).expressionSemantic(sc); eSink.error(e.loc, "argument `%s` has no identifier", o.toErrMsg()); return ErrorExp.get(); } @@ -936,7 +1298,14 @@ Expression semanticTraits(TraitsExp e, Scope* sc) return dimError(1); auto o = (*e.args)[0]; - auto s = getDsymbolWithoutExpCtx(o); + EnumUnionDeclaration variantEnum; + size_t variantIndex; + Dsymbol s; + if (rawParentArgument && + enumUnionVariant(rawParentArgument, variantEnum, variantIndex)) + s = variantEnum.variants[variantIndex].descriptor; + else + s = getDsymbolWithoutExpCtx(o); if (s) { // https://issues.dlang.org/show_bug.cgi?id=12496 @@ -1371,6 +1740,17 @@ Expression semanticTraits(TraitsExp e, Scope* sc) } if (e.ident == Id.getAttributes) { + if (dim == 1) + { + EnumUnionDeclaration variantEnum; + size_t variantIndex; + if (enumUnionVariant((*e.args)[0], variantEnum, variantIndex)) + { + auto udad = variantEnum.variants[variantIndex].descriptor.userAttribDecl; + auto attributes = udad ? udad.getAttributes() : new Expressions(); + return (new TupleExp(e.loc, attributes)).expressionSemantic(sc); + } + } /* Specify 0 for bit 0 of the flags argument to semanticTiargs() so that * a symbol should not be folded to a constant. * Bit 1 means don't convert Parameter to Type if Parameter has an identifier @@ -1944,6 +2324,37 @@ Expression semanticTraits(TraitsExp e, Scope* sc) if (dim != 2) return dimError(2); + EnumUnionDeclaration leftEnum; + EnumUnionDeclaration rightEnum; + size_t leftIndex; + size_t rightIndex; + bool canBeVariant(RootObject object) + { + auto type = getType(object); + return !type || type.isTypeIdentifier() !is null; + } + bool leftIsVariant = canBeVariant((*e.args)[0]) && + enumUnionVariant((*e.args)[0], leftEnum, leftIndex); + bool rightIsVariant = canBeVariant((*e.args)[1]) && + enumUnionVariant((*e.args)[1], rightEnum, rightIndex); + if (leftIsVariant && !rightIsVariant) + { + Objects rightArgument; + rightArgument.push((*e.args)[1]); + if (TemplateInstance_semanticTiargs(e.loc, sc, &rightArgument, 0)) + rightIsVariant = enumUnionVariant(rightArgument[0], rightEnum, rightIndex); + } + else if (rightIsVariant && !leftIsVariant) + { + Objects leftArgument; + leftArgument.push((*e.args)[0]); + if (TemplateInstance_semanticTiargs(e.loc, sc, &leftArgument, 0)) + leftIsVariant = enumUnionVariant(leftArgument[0], leftEnum, leftIndex); + } + if (leftIsVariant || rightIsVariant) + return leftIsVariant && rightIsVariant && leftEnum is rightEnum && + leftIndex == rightIndex ? True() : False(); + // https://issues.dlang.org/show_bug.cgi?id=20761 // tiarg semantic may expand in place the list of arguments, for example: // @@ -2419,9 +2830,10 @@ private void traitNotFound(TraitsExp e) initialized = true; // lazy initialization // All possible traits - __gshared Identifier*[60] idents = + __gshared Identifier*[68] idents = [ &Id.allMembers, + &Id.allVariants, &Id.child, &Id.classInstanceAlignment, &Id.classInstanceSize, @@ -2430,6 +2842,7 @@ private void traitNotFound(TraitsExp e) &Id.fullyQualifiedName, &Id.getAliasThis, &Id.getAttributes, + &Id.variantTag, &Id.getFunctionAttributes, &Id.getFunctionVariadicStyle, &Id.getLinkage, @@ -2444,10 +2857,12 @@ private void traitNotFound(TraitsExp e) &Id.getVirtualFunctions, &Id.getVirtualIndex, &Id.getVirtualMethods, + &Id.getVariant, &Id.getVisibility, &Id.hasCopyConstructor, &Id.hasMember, &Id.hasPostblit, + &Id.hasVariant, &Id.identifier, &Id.isAbstractClass, &Id.isAbstractFunction, @@ -2481,6 +2896,10 @@ private void traitNotFound(TraitsExp e) &Id.isZeroInit, &Id.parameters, &Id.parent, + &Id.variantParams, + &Id.variantParamNames, + &Id.variantKind, + &Id.variantDeclarationOf, ]; StringTable!(bool)* stringTable = cast(StringTable!(bool)*) &traitsStringTable; diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index 65a851e2cb74..3abdb4d300b9 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -6070,6 +6070,11 @@ void resolve(Type mt, Loc loc, Scope* sc, out Expression pe, out Type pt, out Ds pt = mt.obj.isType(); ps = mt.obj.isDsymbol(); pe = mt.obj.isExpression(); + if (auto variant = ps ? ps.isEnumUnionCaseDeclaration() : null) + { + ps = null; + pt = variant.type.addMod(mt.mod); + } return; } @@ -6154,7 +6159,11 @@ void resolve(Type mt, Loc loc, Scope* sc, out Expression pe, out Type pt, out Ds returnType(t); } else if (auto s = mt.obj.isDsymbol()) + { + if (auto variant = s.isEnumUnionCaseDeclaration()) + returnType(variant.type.addMod(mt.mod)); returnSymbol(s); + } else if (auto e = mt.obj.isExpression()) returnExp(e); } @@ -6764,6 +6773,19 @@ Expression dotExp(Type mt, Scope* sc, Expression e, Identifier ident, DotExpFlag return mt.getProperty(sc, e.loc, ident, flag & 1); } + auto eu = mt.sym.isEnumUnionDeclaration(); + if (eu) + { + if (ident == Id.__tag && e.op != EXP.type) + { + e = e.expressionSemantic(sc); + auto tag = new DotVarExp(e.loc, e, eu.tagVar); + tag.type = eu.tagVar.type.addMod(e.type.mod); + auto value = new AddExp(e.loc, tag, new IntegerExp(e.loc, 0, Type.tint32)); + return (new CastExp(e.loc, value, eu.tagVar.type)).expressionSemantic(sc); + } + } + /* If e.tupleof */ if (ident == Id._tupleof) @@ -6788,6 +6810,8 @@ Expression dotExp(Type mt, Scope* sc, Expression e, Identifier ident, DotExpFlag for (size_t i = 0; i < mt.sym.fields.length; i++) { VarDeclaration v = mt.sym.fields[i]; + if (eu && v is eu.tagVar) + continue; Expression ex; if (ev) ex = new DotVarExp(e.loc, ev, v); @@ -7821,6 +7845,8 @@ Dsymbol toDsymbol(Type type, Scope* sc) Dsymbol visitTraits(TypeTraits type) { + if (!sc) + return null; Type t; Expression e; Dsymbol s; @@ -7835,6 +7861,8 @@ Dsymbol toDsymbol(Type type, Scope* sc) Dsymbol visitMixin(TypeMixin type) { + if (!sc) + return null; Type t; Expression e; Dsymbol s; @@ -7866,6 +7894,8 @@ Dsymbol toDsymbol(Type type, Scope* sc) Dsymbol visitInstance(TypeInstance type) { + if (!sc) + return null; Type t; Expression e; Dsymbol s; @@ -7879,6 +7909,8 @@ Dsymbol toDsymbol(Type type, Scope* sc) Dsymbol visitTypeof(TypeTypeof type) { //printf("TypeTypeof::toDsymbol('%s')\n", toChars()); + if (!sc) + return null; Expression e; Type t; Dsymbol s; @@ -7888,6 +7920,8 @@ Dsymbol toDsymbol(Type type, Scope* sc) Dsymbol visitReturn(TypeReturn type) { + if (!sc) + return null; Expression e; Type t; Dsymbol s; diff --git a/compiler/src/dmd/visitor/parsetime.d b/compiler/src/dmd/visitor/parsetime.d index 8109a599701a..d200d1662f3b 100644 --- a/compiler/src/dmd/visitor/parsetime.d +++ b/compiler/src/dmd/visitor/parsetime.d @@ -52,6 +52,7 @@ public: void visit(AST.FuncDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.AliasDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.TupleDeclaration s) { visit(cast(AST.Declaration)s); } + void visit(AST.EnumUnionCaseDeclaration s) { visit(cast(AST.Declaration)s); } // FuncDeclarations void visit(AST.FuncLiteralDeclaration s) { visit(cast(AST.FuncDeclaration)s); } @@ -89,6 +90,7 @@ public: void visit(AST.Module s) { visit(cast(AST.Package)s); } void visit(AST.StructDeclaration s) { visit(cast(AST.AggregateDeclaration)s); } void visit(AST.UnionDeclaration s) { visit(cast(AST.StructDeclaration)s); } + void visit(AST.EnumUnionDeclaration s) { visit(cast(AST.StructDeclaration)s); } void visit(AST.ClassDeclaration s) { visit(cast(AST.AggregateDeclaration)s); } void visit(AST.InterfaceDeclaration s) { visit(cast(AST.ClassDeclaration)s); } void visit(AST.TemplateMixin s) { visit(cast(AST.TemplateInstance)s); } @@ -204,6 +206,7 @@ public: void visit(AST.TupleExp e) { visit(cast(AST.Expression)e); } void visit(AST.ThisExp e) { visit(cast(AST.Expression)e); } void visit(AST.GenericExp e) { visit(cast(AST.Expression)e); } + void visit(AST.SwitchExp e) { visit(cast(AST.Expression)e); } // Miscellaneous void visit(AST.VarExp e) { visit(cast(AST.SymbolExp)e); } diff --git a/compiler/src/dmd/visitor/strict.d b/compiler/src/dmd/visitor/strict.d index 540c7aeaa71e..7ad91477ecc3 100644 --- a/compiler/src/dmd/visitor/strict.d +++ b/compiler/src/dmd/visitor/strict.d @@ -28,6 +28,7 @@ extern(C++) class StrictVisitor(AST) : ParseTimeVisitor!AST override void visit(AST.AliasDeclaration) { assert(0); } override void visit(AST.AliasAssign) { assert(0); } override void visit(AST.TupleDeclaration) { assert(0); } + override void visit(AST.EnumUnionCaseDeclaration) { assert(0); } override void visit(AST.FuncLiteralDeclaration) { assert(0); } override void visit(AST.PostBlitDeclaration) { assert(0); } override void visit(AST.CtorDeclaration) { assert(0); } @@ -61,6 +62,7 @@ extern(C++) class StrictVisitor(AST) : ParseTimeVisitor!AST override void visit(AST.Module) { assert(0); } override void visit(AST.StructDeclaration) { assert(0); } override void visit(AST.UnionDeclaration) { assert(0); } + override void visit(AST.EnumUnionDeclaration) { assert(0); } override void visit(AST.ClassDeclaration) { assert(0); } override void visit(AST.InterfaceDeclaration) { assert(0); } override void visit(AST.TemplateMixin) { assert(0); } @@ -147,6 +149,7 @@ extern(C++) class StrictVisitor(AST) : ParseTimeVisitor!AST override void visit(AST.TypeExp) { assert(0); } override void visit(AST.ScopeExp) { assert(0); } override void visit(AST.IdentifierExp) { assert(0); } + override void visit(AST.SwitchExp) { assert(0); } override void visit(AST.UnaExp) { assert(0); } override void visit(AST.DefaultInitExp) { assert(0); } override void visit(AST.BinExp) { assert(0); } diff --git a/compiler/src/dmd/visitor/transitive.d b/compiler/src/dmd/visitor/transitive.d index 852a0ce37cfa..7b98fcb17f12 100644 --- a/compiler/src/dmd/visitor/transitive.d +++ b/compiler/src/dmd/visitor/transitive.d @@ -801,6 +801,32 @@ package(dmd.visitor) mixin template ParseVisitMethods(AST) s.accept(this); } + override void visit(AST.EnumUnionDeclaration d) + { + foreach (ref variant; d.variants) + { + visitArgs(variant.udas.peekSlice()); + foreach (payload; variant.payload) + visitType(payload); + if (variant.members) + foreach (member; *variant.members) + member.accept(this); + if (variant.variantSplice) + variant.variantSplice.accept(this); + } + if (!d.members) + return; + foreach (member; *d.members) + { + if (member is d.tagVar) + continue; + if (auto attribute = member.isAttribDeclaration()) + if (d.payloadUnion && attribute.decl is d.payloadUnion.members) + continue; + member.accept(this); + } + } + override void visit(AST.ClassDeclaration d) { //printf("Visiting ClassDeclaration\n"); diff --git a/compiler/test/compilable/enum_union_alias_bare_type.d b/compiler/test/compilable/enum_union_alias_bare_type.d new file mode 100644 index 000000000000..b9b3cd64c5f0 --- /dev/null +++ b/compiler/test/compilable/enum_union_alias_bare_type.d @@ -0,0 +1,27 @@ +class C1 {} +class C2 : C1 {} + +enum union Pointers +{ + case First = C1; + case Second = C2; +} + +string classify(Pointers value) +{ + return switch (value) + { + case C1 => "C1", + case C2 => "C2", + }; +} + +void main() +{ + assert(classify(Pointers(new C1)) == "C1"); + assert(classify(Pointers(new C2)) == "C2"); + Pointers.First first = new C1; + Pointers.Second second = new C2; + assert(classify(Pointers.First(first)) == "C1"); + assert(classify(Pointers.Second(second)) == "C2"); +} diff --git a/compiler/test/compilable/enum_union_attributes.d b/compiler/test/compilable/enum_union_attributes.d new file mode 100644 index 000000000000..5757572e9a9c --- /dev/null +++ b/compiler/test/compilable/enum_union_attributes.d @@ -0,0 +1,84 @@ +module enum_union_attributes; + +enum Marker; + +deprecated enum union DeprecatedValue +{ + case None(); +} + +deprecated("use ReplacementValue") enum union DeprecatedMessageValue +{ + case None(); +} + +align(16) enum union AlignedValue +{ + case long; +} + +extern(C) enum union CValue +{ + case int; +} + +extern(C++) enum union CppValue +{ + case int; +} + +export enum union ExportedValue +{ + case int; +} + +package enum union PackageValue +{ + case int; +} + +@Marker enum union AnnotatedValue +{ + case int; +} + +struct Container +{ + private align(8) enum union NestedValue + { + case int; + } + + static assert(__traits(getProtection, NestedValue) == "private"); + static assert(NestedValue.alignof == 8); +} + +static assert(__traits(isDeprecated, DeprecatedValue)); +static assert(__traits(isDeprecated, DeprecatedMessageValue)); +static assert(AlignedValue.alignof == 16); +static assert(__traits(getLinkage, CValue) == "C"); +static assert(__traits(getLinkage, CppValue) == "C++"); +static assert(__traits(getProtection, ExportedValue) == "export"); +static assert(__traits(getProtection, PackageValue) == "package"); +static assert(__traits(getAttributes, AnnotatedValue).length == 1); + +void acceptAttributes() +{ + deprecated enum union LocalDeprecatedValue + { + case None(); + } + + align(8) enum union LocalAlignedValue + { + case int; + } + + static assert(__traits(isDeprecated, LocalDeprecatedValue)); + static assert(LocalAlignedValue.alignof == 8); + + AlignedValue alignedValue = long(1); + CValue cValue = int(2); + ExportedValue exportedValue = int(3); + PackageValue packageValue = int(4); +} diff --git a/compiler/test/compilable/enum_union_bare_construct_pointer.d b/compiler/test/compilable/enum_union_bare_construct_pointer.d new file mode 100644 index 000000000000..443a73148c6b --- /dev/null +++ b/compiler/test/compilable/enum_union_bare_construct_pointer.d @@ -0,0 +1,21 @@ +enum union Pointers +{ + case int*; + case bool*; +} + +int classify(Pointers pointers) +{ + return switch (pointers) + { + case int* => 1, + case bool* => 2, + }; +} + +void main() +{ + int value; + auto pointers = Pointers(&value); + assert(classify(pointers) == 1); +} diff --git a/compiler/test/compilable/enum_union_complex_transition.d b/compiler/test/compilable/enum_union_complex_transition.d new file mode 100644 index 000000000000..2802602cca1d --- /dev/null +++ b/compiler/test/compilable/enum_union_complex_transition.d @@ -0,0 +1,15 @@ +// REQUIRED_ARGS: -verrors=simple + +/* +TEST_OUTPUT: +--- +compilable/enum_union_complex_transition.d(11): Deprecation: use of complex type `cdouble` is deprecated, use `std.complex.Complex!(double)` instead +compilable/enum_union_complex_transition.d(11): Deprecation: use of imaginary type `idouble` is deprecated, use `double` instead +--- +*/ + +enum union Values +{ + case cdouble; + case idouble; +} diff --git a/compiler/test/compilable/enum_union_constructor_init.d b/compiler/test/compilable/enum_union_constructor_init.d new file mode 100644 index 000000000000..32a7637d6f0b --- /dev/null +++ b/compiler/test/compilable/enum_union_constructor_init.d @@ -0,0 +1,39 @@ +enum union PointersDirect +{ + case int*; + case bool*; + + this(typeof(null) value) + { + this = cast(int*) null; + assert(switch (this) + { + case int* => true, + case bool* => false, + }); + } +} + +PointersDirect pointers1 = null; + +enum union PointersBranch +{ + case int*; + case bool*; + + this(typeof(null) value, bool selectInt) + { + if (selectInt) + this = cast(int*) null; + else + this = cast(bool*) null; + + assert(switch (this) + { + case int* => true, + case bool* => true, + }); + } +} + +PointersBranch pointers2 = PointersBranch(null, true); diff --git a/compiler/test/compilable/enum_union_header_generation.d b/compiler/test/compilable/enum_union_header_generation.d new file mode 100644 index 000000000000..b1c662150147 --- /dev/null +++ b/compiler/test/compilable/enum_union_header_generation.d @@ -0,0 +1,14 @@ +// REQUIRED_ARGS: -o- -Hf${RESULTS_DIR}/compilable/enum_union_header_generation.di +module enum_union_header_generation; + +enum union Shape +{ + case Circle(double radius); + case Point(); +} + +enum union Option(T) +{ + case Some(T); + case None(); +} diff --git a/compiler/test/compilable/enum_union_header_roundtrip.sh b/compiler/test/compilable/enum_union_header_roundtrip.sh new file mode 100644 index 000000000000..5d6fe42e16a7 --- /dev/null +++ b/compiler/test/compilable/enum_union_header_roundtrip.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +$DMD -o- -Hf"${OUTPUT_BASE}.di" "${EXTRA_FILES}/enum_union_header_roundtrip.d" +$DMD -c -of"${OUTPUT_BASE}.${OBJ}" -I"$(dirname "${OUTPUT_BASE}")" \ + "${EXTRA_FILES}/enum_union_header_roundtrip_consumer.d" diff --git a/compiler/test/compilable/enum_union_member_interleaving.d b/compiler/test/compilable/enum_union_member_interleaving.d new file mode 100644 index 000000000000..3a9588b387cf --- /dev/null +++ b/compiler/test/compilable/enum_union_member_interleaving.d @@ -0,0 +1,27 @@ +enum union InterleavedCases +{ + alias BeforeCases = int; + + case Data(int); + + enum int revision = 1; + + case Ping(long); + alias BetweenCases = long; + + case Reset(short); +} + +enum union InterleavedCaseList +{ + alias BeforeList = int; + + case First(), Second(); + + static int helper() { return 42; } + + case Third(); +} + +static assert(InterleavedCases.revision == 1); +static assert(InterleavedCaseList.helper() == 42); diff --git a/compiler/test/compilable/enum_union_network_event.d b/compiler/test/compilable/enum_union_network_event.d new file mode 100644 index 000000000000..32b7fd23626c --- /dev/null +++ b/compiler/test/compilable/enum_union_network_event.d @@ -0,0 +1,83 @@ +module net.event_processor; + +import core.stdc.stdio; +import std.format : format; + +// 1. Hybrid Tagged Union: Primitives, Slices, Tuples, and Named Records +enum union NetworkEvent +{ + // Bare primitive & slice types (types act directly as discriminant tags) + case int, // Raw error code + case ubyte[], // Unparsed raw payload buffer + + // Unit variants + case Disconnected(); + case Heartbeat(); + + // Positional (tuple-like) variants + case Ping(ulong timestamp, ushort sequenceId); + + // Named record variants + case HttpRequest { string method; string path; ushort statusCode; }; + + // Embedded methods + string summary() const @safe + { + // Switch expression with fat-arrow arms and comma separators + return switch (this) + { + case int errCode => format("Socket Error: %d", errCode), + case ubyte[] data => format("Raw Frame (%d bytes)", data.length), + case Disconnected() => "Connection Closed", + case Heartbeat() => "Keep-Alive ACK", + case Ping(ts, seq) => format("Ping [seq=%d, ts=%d]", seq, ts), + case HttpRequest { method, path, statusCode } => format("%s %s -> %d", method, path, statusCode), + }; + } +} + +// 2. Request Dispatcher demonstrating elimination and return type unification +struct ConnectionHandler +{ + ulong activeSessionId; + + // Dispatches an incoming event and computes an action response code + int handleEvent(NetworkEvent event) @safe + { + // All arms strictly unify via Least Upper Bound (LUB) + return switch (event) + { + case int err => err < 0 ? err : -1, + case ubyte[] frame => processFrame(frame), + case Heartbeat() => 0, + case Ping(ts, seq) => sendPong(ts, seq), + case HttpRequest { statusCode, ... } => cast(int) statusCode, // Partial record destructuring + case Disconnected() => throw new Exception("Terminating disconnected session"), + }; + } + + private int processFrame(const ubyte[] frame) @safe pure nothrow => 200; + private int sendPong(ulong ts, ushort seq) @safe nothrow => 1; +} + +void main() +{ + // Supports assignment-style construction + NetworkEvent e1 = 404; + ubyte[] payload = [0xDE, 0xAD, 0xBE, 0xEF]; + NetworkEvent e2 = payload; + + // Labeled variant construction via synthesized static factories for named case variants + NetworkEvent e3 = NetworkEvent.Heartbeat; + NetworkEvent e4 = NetworkEvent.Ping(1_700_000_000, 42); + NetworkEvent e5 = NetworkEvent.HttpRequest("GET", "/api/v1/status", 200); + + auto handler = ConnectionHandler(1001); + + assert(handler.handleEvent(e1) == -1); + assert(handler.handleEvent(e3) == 0); + assert(handler.handleEvent(e4) == 1); + assert(handler.handleEvent(e5) == 200); + assert(e2.summary() == "Raw Frame (4 bytes)"); + assert(e5.summary() == "GET /api/v1/status -> 200"); +} diff --git a/compiler/test/compilable/enum_union_omitted_trailing_delimiter.d b/compiler/test/compilable/enum_union_omitted_trailing_delimiter.d new file mode 100644 index 000000000000..a5b5dc3e270c --- /dev/null +++ b/compiler/test/compilable/enum_union_omitted_trailing_delimiter.d @@ -0,0 +1,34 @@ +// Test that omitting the trailing semicolon or comma on the final variant +// immediately before the closing right curly brace is accepted without error. + +enum union SingleLineOmitted +{ + case First(), Second(), int, Tuple(int, int) +} + +enum union MultiLineOmitted +{ + case First(); + case Second(); + case int; + case Tuple(int, int) +} + +enum union CommaSeparatedMultiLineOmitted +{ + case First(), + Second(), + int, + Tuple(int, int) +} + +void test() +{ + SingleLineOmitted s = SingleLineOmitted.First(); + MultiLineOmitted m = MultiLineOmitted.Second(); + CommaSeparatedMultiLineOmitted c = CommaSeparatedMultiLineOmitted.Tuple(1, 2); + + assert(s.__tag == 0); + assert(m.__tag == 1); + assert(c.__tag == 3); +} diff --git a/compiler/test/compilable/enum_union_patterns.d b/compiler/test/compilable/enum_union_patterns.d new file mode 100644 index 000000000000..9834233131ad --- /dev/null +++ b/compiler/test/compilable/enum_union_patterns.d @@ -0,0 +1,159 @@ +module enum_union_patterns; + +class C1 {} +class C2 : C1 {} +struct S {} + +enum union Bare +{ + case C1; + case C2; + case S; +} + +enum union Named +{ + case Unit(); + case Tuple(int); +} + +string classifyBare(Bare value) +{ + return switch (value) + { + case C1 => "C1", + case C2 => "C2", + case S => "S", + }; +} + +string classifyNamed(Named value) +{ + return switch (value) + { + case Unit => "unit", + case Tuple => "tuple", + }; +} + +string classifyUnitCall(Named value) +{ + return switch (value) + { + case Unit() => "unit", + case Tuple => "tuple", + }; +} + +// Qualified patterns +enum union QualValue +{ + case C1; + case C3 = C2; + case Unit(); + case Tuple(int); +} + +string classifyQual(QualValue value) +{ + return switch (value) + { + case C1 => "C1", + case QualValue.C3 => "C2", + case QualValue.Unit => "unit", + case QualValue.Tuple => "tuple", + }; +} + +string classifyByType(QualValue value) +{ + return switch (value) + { + case C1 => "C1", + case enum_union_patterns.C2 => "C2", + case QualValue.Unit => "unit", + case QualValue.Tuple => "tuple", + }; +} + +string classifyExplicitUnit(QualValue value) +{ + return switch (value) + { + case QualValue.Unit() => "unit", + case QualValue.Tuple => "tuple", + case C1 => "C1", + case C2 => "C2", + }; +} + +bool classifyQualifiedBinding(QualValue value) +{ + return switch (value) + { + case enum_union_patterns.C2 instance => instance !is null, + default => false, + }; +} + +// Named pattern bindings +enum union BoundValue +{ + case Unit(); + case Pair(int left, int right); + case Record { int value; }; +} + +enum union Units +{ + case First(); + case Second(); +} + +auto unitPayload(Units value) +{ + return switch (value) + { + case First payload => payload, + case Second payload => payload, + }; +} + +static assert(unitPayload(Units.First).sizeof == 1); + +enum left = 100; + +int pairLeft(BoundValue value) +{ + return switch (value) + { + case Pair(left, right) => left, + default => 0, + }; +} + +static assert(pairLeft(BoundValue.Pair(1, 2)) == 1); + +void main() +{ + assert(classifyBare(Bare(new C2)) == "C2"); + assert(classifyBare(Bare(S())) == "S"); + assert(classifyNamed(Named.Unit) == "unit"); + assert(classifyNamed(Named.Tuple(1)) == "tuple"); + assert(classifyUnitCall(Named.Unit()) == "unit"); + + assert(classifyQual(QualValue(new C1)) == "C1"); + assert(classifyQual(QualValue(new C2)) == "C2"); + assert(classifyQual(QualValue.Unit) == "unit"); + assert(classifyQual(QualValue.Tuple(1)) == "tuple"); + assert(classifyByType(QualValue(new C2)) == "C2"); + assert(classifyExplicitUnit(QualValue.Unit()) == "unit"); + assert(classifyQualifiedBinding(QualValue(new C2))); + + assert(switch (BoundValue.Unit) + { + case Unit value => cast(int) value.sizeof, + case Pair value => value.left + value.right, + case Record value => value.value, + } == 1); +} diff --git a/compiler/test/compilable/enum_union_switch_property_condition.d b/compiler/test/compilable/enum_union_switch_property_condition.d new file mode 100644 index 000000000000..796e352faacb --- /dev/null +++ b/compiler/test/compilable/enum_union_switch_property_condition.d @@ -0,0 +1,16 @@ +enum union Value +{ + case Unit(); + case Number(int); +} + +string describe() +{ + return switch (Value.Unit) + { + case Unit => "unit", + case Number => "number", + }; +} + +static assert(describe() == "unit"); diff --git a/compiler/test/compilable/enum_union_switch_static_foreach.d b/compiler/test/compilable/enum_union_switch_static_foreach.d new file mode 100644 index 000000000000..c01f670e37d6 --- /dev/null +++ b/compiler/test/compilable/enum_union_switch_static_foreach.d @@ -0,0 +1,103 @@ +// Test static foreach and static if inside switch expressions + +template AliasSeq(T...) +{ + alias AliasSeq = T; +} + +template Filter(alias Pred, T...) +{ + static if (T.length == 0) + alias Filter = AliasSeq!(); + else static if (Pred!(T[0])) + alias Filter = AliasSeq!(T[0], Filter!(Pred, T[1 .. $])); + else + alias Filter = Filter!(Pred, T[1 .. $]); +} + +enum union DynamicNumber +{ + case error(); + case int; + case double; +} + +DynamicNumber sum(DynamicNumber a, DynamicNumber b) @safe +{ + enum isBare(alias V) = __traits(variantKind, V) == "bare"; + alias NumericVariants = Filter!(isBare, __traits(allVariants, DynamicNumber)); + + return switch (a) + { + case error() => a, + static foreach (V1; NumericVariants) + case V1 v1 => switch (b) + { + case error() => b, + static foreach (V2; NumericVariants) + case V2 v2 => DynamicNumber(v1 + v2) + } + }; +} + +// Test braced static foreach and static if / else +enum union Shape +{ + case Circle(double); + case Rect(double, double); + case Point(); +} + +double area(Shape s) +{ + return switch (s) + { + static foreach (T; __traits(allVariants, Shape)) + { + static if (__traits(identifier, T) == "Circle") + { + case Circle(r) => 3.14159 * r * r, + } + else static if (__traits(identifier, T) == "Rect") + { + case Rect(w, h) => w * h, + } + else + { + // Optional trailing delimiter before right curly + case Point() => 0.0 + } + } + }; +} + +void test() +{ + DynamicNumber a = DynamicNumber(10); + DynamicNumber b = DynamicNumber(20.5); + DynamicNumber c = sum(a, b); + + bool ok = switch (c) + { + case double d => (d == 30.5), + default => false, + }; + assert(ok); + + Shape sh = Shape.Rect(3.0, 4.0); + assert(area(sh) == 12.0); + + // Verify CTFE evaluation + static assert({ + DynamicNumber x = DynamicNumber(5); + DynamicNumber y = DynamicNumber(15); + DynamicNumber z = sum(x, y); + return switch (z) + { + case int i => (i == 20), + default => false, + }; + }()); + + static assert(area(Shape.Rect(5.0, 2.0)) == 10.0); +} diff --git a/compiler/test/compilable/enum_union_test.d b/compiler/test/compilable/enum_union_test.d new file mode 100644 index 000000000000..faab5566dda9 --- /dev/null +++ b/compiler/test/compilable/enum_union_test.d @@ -0,0 +1,160 @@ +module enumsunion_test; + +import core.stdc.stdio; + +// 1. Basic Unit & Positional Variants +enum union Option(T) { + case Some(T); + case None(); +} + +// 2. Hybrid Union: Bare Types, Records, and Unit Sentinels +enum union Response { + case double, // Bare type + case string, // Bare type + case Timeout(), // Unit variant + case Success { int code; string payload; } // Record variant; +} + +// 3. RAII Resource Tracking +struct Resource { + int id; + static int liveCount = 0; + this(int id) { this.id = id; liveCount++; } + this(ref typeof(this) rhs) { this.id = rhs.id; liveCount++; } + ~this() { liveCount--; } +} + +enum union Managed { + case Handle(Resource); + case Empty(); +} + +// 4. Field access across variants +enum union Entity { + case Player { int id; string name; }; + case Monster { int id; int hp; }; +} + +struct Box(T) { + T value; +} + +struct Struct { + int n; +} + +enum union GenericValue { + case Box!int; + case Struct = .Struct; + case Done(); +} + +void main() { + // Test Option Matching & Type Unification + Option!int opt = Option!int.Some(42); + int val = switch (opt) { + case Some(v) => v * 2, + case None() => 0, + }; + assert(val == 84); + + // Test 'noreturn' arm unification + int safeVal = switch (opt) { + case Some(v) => v, + case None() => throw new Exception("Empty"), // Unifies: LUB(int, noreturn) -> int + }; + assert(safeVal == 42); + + // Test Bare Type Matching and Direct Construction + Response r1 = 3.14; + Response r2 = Response.Success(200, "OK"); + Response r3 = Response.Timeout; + string status = switch (r1) { + case double d => "Floating-point", + case string s => "String", + case Timeout() => "Timeout", + case Success { code, ... } => "Success", + }; + assert(status == "Floating-point"); + + int successCode = switch (r2) { + case double d => -1, + case string s => -2, + case Timeout() => -3, + case Success { code, ... } => code, + }; + assert(successCode == 200); + + // Test RAII Destruction and Re-tagging + { + assert(Resource.liveCount == 0); + Managed m = Managed.Handle(Resource(1)); + assert(Resource.liveCount == 1); + + m = Managed.Empty; // Overwrite must invoke Resource.~this() + assert(Resource.liveCount == 0); + } + assert(Resource.liveCount == 0); + + // Field access across variants goes through a switch, not direct `.field`. + Entity e = Entity.Player(10, "Hero"); + int id = switch (e) { + case Player { id, ... } => id, + case Monster { id, ... } => id, + }; + assert(id == 10); + + GenericValue generic = Box!int(7); + int genericValue = switch (generic) { + case Box!int box => box.value, + case Struct value => value.n, + case Done() => 0, + }; + assert(genericValue == 7); + + GenericValue structValue = Struct(42); + int structResult = switch (structValue) { + case Struct { n } => n, + case Box!int box => box.value, + case Done() => 0, + }; + assert(structResult == 42); + + GenericValue factoryValue = GenericValue.Struct(43); + assert(factoryValue.__tag == 1); + + // Default init tag + Option!int defaultOpt = Option!int.init; + assert(defaultOpt.__tag == 0); + + // Static local enum union + static enum union LocalEnum + { + case int; + case string; + } + LocalEnum localVal = 1; + assert(localVal.__tag == 0); +} + +// is-expression specializer +static assert(is(Response == enum union)); +static assert(!is(int == enum union)); +static assert(!is(Resource == enum union)); + +// CTFE switch expression matching active payload +enum union CtfeVal +{ + case First(int); + case Second(int); +} + +enum ctfeInstance = CtfeVal.Second(42); +enum ctfeResult = switch (ctfeInstance) +{ + case First(...) => 0, + case Second(number) if (number == 42) => 42, + case Second(...) => 0, +}; +static assert(ctfeResult == 42); diff --git a/compiler/test/compilable/enum_union_uda.d b/compiler/test/compilable/enum_union_uda.d new file mode 100644 index 000000000000..034b9d58e31c --- /dev/null +++ b/compiler/test/compilable/enum_union_uda.d @@ -0,0 +1,47 @@ +struct MyUDA {} + +enum union Tag +{ + @MyUDA case Number(int); + case Empty(); +} + +static assert(__traits(getAttributes, Tag.Number).length == 1); + +enum union BareTag +{ + @MyUDA case int; + case bool; +} + +@(Tag.Number(42)) +struct NumberAnnotated {} + +@(Tag.Empty) +struct EmptyAnnotated {} + +@(BareTag(true)) +struct BareAnnotated {} + +enum numberAttribute = __traits(getAttributes, NumberAnnotated)[0]; +enum emptyAttribute = __traits(getAttributes, EmptyAnnotated)[0]; +enum bareAttribute = __traits(getAttributes, BareAnnotated)[0]; + +static assert(numberAttribute.__tag == 0); +static assert(emptyAttribute.__tag == 1); +static assert(bareAttribute.__tag == 1); + +void main() +{ + assert(switch (numberAttribute) + { + case Number(value) => value == 42, + case Empty() => false, + }); + + assert(switch (bareAttribute) + { + case int => false, + case bool => true, + }); +} diff --git a/compiler/test/compilable/enum_union_variant_grammar.d b/compiler/test/compilable/enum_union_variant_grammar.d new file mode 100644 index 000000000000..2f0e964955e6 --- /dev/null +++ b/compiler/test/compilable/enum_union_variant_grammar.d @@ -0,0 +1,88 @@ +struct ExternalStruct +{ + int id; +} + +struct Wrapper(T) +{ + T value; +} + +enum union Result(T) +{ + case Success(T); + case Failure(); + case ExternalStruct; + case Wrapper!int; + case string; +} + +enum union NamedUnit +{ + case ExternalStruct(); +} + +enum union VariantPack(Types...) +{ + case Empty(); + static foreach (T; Types) + case T; +} + +alias MyResult = Result!int; +alias MyPack = VariantPack!(ExternalStruct, int); + +int resultValue(MyResult result) +{ + return switch (result) + { + case Success(value) => value, + case Failure() => -1, + case ExternalStruct value => value.id, + case Wrapper!int value => value.value, + case string value => cast(int) value.length, + }; +} + +int packValue(MyPack value) +{ + return switch (value) + { + case Empty() => 0, + case ExternalStruct value => value.id, + case int value => value, + }; +} + +int namedUnitBare(NamedUnit value) +{ + return switch (value) + { + case ExternalStruct => 1, + }; +} + +int namedUnitCall(NamedUnit value) +{ + return switch (value) + { + case ExternalStruct() => 2, + }; +} + +void main() +{ + assert(resultValue(MyResult.Success(1)) == 1); + assert(resultValue(MyResult.Failure()) == -1); + assert(resultValue(ExternalStruct(10)) == 10); + assert(resultValue(Wrapper!int(20)) == 20); + assert(resultValue("hello") == 5); + assert(NamedUnit.ExternalStruct().__tag == + __traits(variantTag, __traits(getVariant, NamedUnit, "ExternalStruct"))); + assert(namedUnitBare(NamedUnit.ExternalStruct) == 1); + assert(namedUnitCall(NamedUnit.ExternalStruct()) == 2); + + assert(packValue(MyPack.Empty()) == 0); + assert(packValue(ExternalStruct(20)) == 20); + assert(packValue(30) == 30); +} diff --git a/compiler/test/compilable/extra-files/enum_union_header_roundtrip.d b/compiler/test/compilable/extra-files/enum_union_header_roundtrip.d new file mode 100644 index 000000000000..d385a339967c --- /dev/null +++ b/compiler/test/compilable/extra-files/enum_union_header_roundtrip.d @@ -0,0 +1,61 @@ +module enum_union_header_roundtrip; + +// 1. Tag access and basic variants +enum union Message +{ + case None(); + case Value(int value); +} + +// 2. Case UDAs and static foreach in template function +struct Marker +{ + string name; +} + +template AliasSeq(T...) +{ + alias AliasSeq = T; +} + +enum union HardenedValue +{ + @Marker("none") case None(); + @Marker("some") case Some(int); +} + +int inspect(T)(HardenedValue value) +{ + return switch (value) + { + static foreach (index; AliasSeq!(0, 1)) + { + static if (index == 0) + case None() => 0, + else + case Some(payload) => payload, + } + }; +} + +// 3. Tuple pattern matching in template function +struct Tuple(T...) +{ + T expand; + alias expand this; +} + +enum union TupleValue +{ + case Wrapped(Tuple!(int, int)); + case Record { Tuple!(int, int) point; }; +} + +int inspectTuple(T)(TupleValue value) +{ + return switch (value) + { + case Wrapped((left, right)) => left + right, + case Record { point: (left, right) } => left + right, + }; +} diff --git a/compiler/test/compilable/extra-files/enum_union_header_roundtrip_consumer.d b/compiler/test/compilable/extra-files/enum_union_header_roundtrip_consumer.d new file mode 100644 index 000000000000..db4ad4576001 --- /dev/null +++ b/compiler/test/compilable/extra-files/enum_union_header_roundtrip_consumer.d @@ -0,0 +1,21 @@ +module enum_union_header_roundtrip_consumer; + +import enum_union_header_roundtrip; + +// 1. Tag access assertions +ubyte testTag(ref Message message) +{ + static assert(!__traits(compiles, message.__tag = 0)); + return message.__tag; +} + +// 2. Case UDA and template function call +static assert(__traits(getAttributes, HardenedValue.None)[0].name == "none"); +static assert(__traits(getAttributes, HardenedValue.Some)[0].name == "some"); +static assert(inspect!void(HardenedValue.Some(7)) == 7); + +// 3. Tuple pattern call +int useTuple(TupleValue value) +{ + return inspectTuple!void(value); +} diff --git a/compiler/test/compilable/switch_expression_statement.d b/compiler/test/compilable/switch_expression_statement.d new file mode 100644 index 000000000000..89c66e5f5600 --- /dev/null +++ b/compiler/test/compilable/switch_expression_statement.d @@ -0,0 +1,32 @@ +enum union Value +{ + case Unit(); + case Number(int); +} + +void consume() +{ +} + +void main() +{ + switch (Value.Unit) + { + case Unit => consume(), + case Number value => consume(), + } + + switch (Value.Unit) + { + case Unit value => "unit", + case Number value => { consume(); return "number"; }(), + } + + switch (0) + { + case 0: + break; + default: + break; + } +} diff --git a/compiler/test/compilable/testlambdacomp.d b/compiler/test/compilable/testlambdacomp.d index e2efdb02515c..117d44f84b1d 100644 --- a/compiler/test/compilable/testlambdacomp.d +++ b/compiler/test/compilable/testlambdacomp.d @@ -4,7 +4,7 @@ module testlambdacomp; void test1() { static assert(__traits(isSame, (a, b) => a + b, (c, d) => c + d)); - static assert(__traits(isSame, a => ++a, b => ++b)); + static assert(__traits(isSame, a => ++a, b => ++b)); static assert(!__traits(isSame, (int a, int b) => a + b, (a, b) => a + b)); static assert(__traits(isSame, (a, b) => a + b + 10, (c, d) => c + d + 10)); } @@ -63,10 +63,10 @@ void test2() B q = new B(7); alias pred = (A a, A b) => ++a.a.x + (--b.a.x); foo3!pred(); - static assert(!__traits(isSame, (A a) => ++a.a.x + 2, (A b) => ++b.a.x + 3)); + static assert(!__traits(isSame, (A a) => ++a.a.x + 2, (A b) => ++b.a.x + 3)); static assert(__traits(isSame, pred, (A x, A y) => ++x.a.x + (--y.a.x))); - static assert(!__traits(isSame, (B a) => ++a.a + 2, (B b) => ++b.a + 3)); - static assert(__traits(isSame, (B a) => ++a.a, (B a) => ++a.a)); + static assert(!__traits(isSame, (B a) => ++a.a + 2, (B b) => ++b.a + 3)); + static assert(__traits(isSame, (B a) => ++a.a, (B a) => ++a.a)); B cl = new B(7); static assert(!__traits(isSame, a => a + q.a, c => c + cl.a)); @@ -106,7 +106,7 @@ void test3() } enum r1 = Bar(1); enum r2 = Bar(1); - static assert(__traits(isSame, a => a + r1.a, b => b + r2.a)); + static assert(__traits(isSame, a => a + r1.a, b => b + r2.a)); enum X { A, B, C} static assert(__traits(isSame, a => a + X.A, a => a + 0)); diff --git a/compiler/test/fail_compilation/enum_union_alias_case_self.d b/compiler/test/fail_compilation/enum_union_alias_case_self.d new file mode 100644 index 000000000000..7ab0ddc67d28 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_alias_case_self.d @@ -0,0 +1,13 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_alias_case_self.d(12): Error: `case C1 = C1` cannot alias itself, use a qualified name +--- +*/ + +class C1 {} + +enum union Pointers +{ + case C1 = C1; +} diff --git a/compiler/test/fail_compilation/enum_union_bare_call.d b/compiler/test/fail_compilation/enum_union_bare_call.d new file mode 100644 index 000000000000..489d3d5624b5 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_bare_call.d @@ -0,0 +1,34 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_bare_call.d(21): Error: enum union `SameSigFuns` does not overload () +fail_compilation/enum_union_bare_call.d(33): Error: enum union `DiffSigFuns` does not overload () +--- +*/ + +// Calling an enum union value directly is not supported regardless of whether +// callable variants share the same signature or have differing signatures. + +enum union SameSigFuns +{ + case int function(int); + case int delegate(int); +} + +void testSame() +{ + SameSigFuns f = delegate(int x) { return 0; }; + assert(f(42) == 0); +} + +enum union DiffSigFuns +{ + case int function(int); + case void delegate(string); +} + +void testDiff() +{ + DiffSigFuns f = delegate(string s) {}; + f(); +} diff --git a/compiler/test/fail_compilation/enum_union_bare_construct_ambiguous.d b/compiler/test/fail_compilation/enum_union_bare_construct_ambiguous.d new file mode 100644 index 000000000000..4d3f5f3f7cef --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_bare_construct_ambiguous.d @@ -0,0 +1,52 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_bare_construct_ambiguous.d(47): Error: `[]` is ambiguous between variants `int[]` and `void[]` of enum union `enum_union_bare_construct_ambiguous.Arrs` +fail_compilation/enum_union_bare_construct_ambiguous.d(48): Error: `() { }` is ambiguous between variants `void function()` and `void delegate()` of enum union `enum_union_bare_construct_ambiguous.Funs` +fail_compilation/enum_union_bare_construct_ambiguous.d(49): Error: `enum_union_bare_construct_ambiguous.Pointers.__ctor` called with argument types `(typeof(null))` matches multiple overloads after qualifier conversion: +fail_compilation/enum_union_bare_construct_ambiguous.d(33): `enum_union_bare_construct_ambiguous.Pointers.this(int* __enumPayloadParam33)` +and: +fail_compilation/enum_union_bare_construct_ambiguous.d(33): `enum_union_bare_construct_ambiguous.Pointers.this(bool* __enumPayloadParam34)` +fail_compilation/enum_union_bare_construct_ambiguous.d(50): Error: `enum_union_bare_construct_ambiguous.Integral.__ctor` called with argument types `(int)` matches multiple overloads after implicit conversions: +fail_compilation/enum_union_bare_construct_ambiguous.d(39): `enum_union_bare_construct_ambiguous.Integral.this(short __enumPayloadParam41)` +and: +fail_compilation/enum_union_bare_construct_ambiguous.d(39): `enum_union_bare_construct_ambiguous.Integral.this(ushort __enumPayloadParam42)` +fail_compilation/enum_union_bare_construct_ambiguous.d(51): Error: `enum_union_bare_construct_ambiguous.Pointers.__ctor` called with argument types `(typeof(null))` matches multiple overloads after qualifier conversion: +fail_compilation/enum_union_bare_construct_ambiguous.d(33): `enum_union_bare_construct_ambiguous.Pointers.this(int* __enumPayloadParam33)` +and: +fail_compilation/enum_union_bare_construct_ambiguous.d(33): `enum_union_bare_construct_ambiguous.Pointers.this(bool* __enumPayloadParam34)` +--- +*/ + +enum union Arrs +{ + case int[]; + case void[]; +} + +enum union Funs +{ + case void function(); + case void delegate(); +} + +enum union Pointers +{ + case int*; + case bool*; +} + +enum union Integral +{ + case short; + case ushort; +} + +void main() +{ + Arrs arrs = []; + Funs f = (){}; + Pointers p1 = Pointers(null); + Integral i = 0; + Pointers p2 = null; +} diff --git a/compiler/test/fail_compilation/enum_union_base_type.d b/compiler/test/fail_compilation/enum_union_base_type.d new file mode 100644 index 000000000000..0b2ccc0e7980 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_base_type.d @@ -0,0 +1,11 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_base_type.d(8): Error: enum union declarations cannot have a base type +--- +*/ + +enum union Value : int +{ + case None(); +} diff --git a/compiler/test/fail_compilation/enum_union_case_missing_semicolon.d b/compiler/test/fail_compilation/enum_union_case_missing_semicolon.d new file mode 100644 index 000000000000..d52a2ca3c1c8 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_case_missing_semicolon.d @@ -0,0 +1,20 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_case_missing_semicolon.d(12): Error: `,` or `;` expected after enum union variant +fail_compilation/enum_union_case_missing_semicolon.d(19): Error: `,` or `;` expected after enum union variant +--- +*/ + +enum union MissingBetweenCases +{ + case First() + case Second(); + case Third(); +} + +enum union MissingBeforeMember +{ + case One(), Two() + void helper() {} +} diff --git a/compiler/test/fail_compilation/enum_union_const_mutation.d b/compiler/test/fail_compilation/enum_union_const_mutation.d new file mode 100644 index 000000000000..3a7c6342d313 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_const_mutation.d @@ -0,0 +1,23 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_const_mutation.d(21): Error: cannot modify `const` expression `*p` +fail_compilation/enum_union_const_mutation.d(19): Error: switch expression has no effect; use `cast(void)` to discard its value +--- +*/ + +enum union Value +{ + case Ptr(int*); +} + +void main() +{ + int x = 42; + const Value u = Value.Ptr(&x); + + switch (u) + { + case Ptr(p) => *p = 999, + } +} diff --git a/compiler/test/fail_compilation/enum_union_constructor_read_this.d b/compiler/test/fail_compilation/enum_union_constructor_read_this.d new file mode 100644 index 000000000000..4520eca44714 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_constructor_read_this.d @@ -0,0 +1,42 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_constructor_read_this.d(17): Error: cannot read `this` in constructor `enum_union_constructor_read_this.Pointers1.this` before it is initialized +fail_compilation/enum_union_constructor_read_this.d(36): Error: cannot read `this` in constructor `enum_union_constructor_read_this.Pointers2.this` before it is initialized +--- +*/ + +// 1. Read before any initialization +enum union Pointers1 +{ + case int*; + case bool*; + + this(typeof(null) value) + { + this = switch (this) + { + case int* => cast(bool*) null, + case bool* => new bool(), + }; + } +} + +// 2. Read after partial branch initialization +enum union Pointers2 +{ + case int*; + case bool*; + + this(typeof(null) value, bool selectInt) + { + if (selectInt) + this = cast(int*) null; + + assert(switch (this) + { + case int* => true, + case bool* => true, + }); + } +} diff --git a/compiler/test/fail_compilation/enum_union_deprecated_message.d b/compiler/test/fail_compilation/enum_union_deprecated_message.d new file mode 100644 index 000000000000..4b9fc1aed04a --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_deprecated_message.d @@ -0,0 +1,17 @@ +/* +REQUIRED_ARGS: -de +TEST_OUTPUT: +--- +fail_compilation/enum_union_deprecated_message.d(17): Deprecation: enum union `enum_union_deprecated_message.OldValue` is deprecated - use NewValue +fail_compilation/enum_union_deprecated_message.d(12): `OldValue` is declared here +fail_compilation/enum_union_deprecated_message.d(17): Deprecation: enum union `enum_union_deprecated_message.OldValue` is deprecated - use NewValue +fail_compilation/enum_union_deprecated_message.d(12): `OldValue` is declared here +--- +*/ + +deprecated("use NewValue") enum union OldValue +{ + case None(); +} + +OldValue value; diff --git a/compiler/test/fail_compilation/enum_union_direct_construction.d b/compiler/test/fail_compilation/enum_union_direct_construction.d new file mode 100644 index 000000000000..9b6fe44154fa --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_direct_construction.d @@ -0,0 +1,16 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_direct_construction.d(15): Error: enum union `enum_union_direct_construction.Value` cannot be constructed directly; use a variant +fail_compilation/enum_union_direct_construction.d(16): Error: enum union `enum_union_direct_construction.Value` cannot be constructed directly; use a variant +--- +*/ + +enum union Value +{ + case Number(int); +} + +Value value = Value.Number(1); +auto direct = Value(value); +auto allocated = new Value(value); diff --git a/compiler/test/fail_compilation/enum_union_disabled_copy_variant.d b/compiler/test/fail_compilation/enum_union_disabled_copy_variant.d new file mode 100644 index 000000000000..1a747450e0f6 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_disabled_copy_variant.d @@ -0,0 +1,32 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_disabled_copy_variant.d(16): Error: copy constructor `enum_union_disabled_copy_variant.DisabledCopy.this` cannot be used because it is annotated with `@disable` +fail_compilation/enum_union_disabled_copy_variant.d(28): Error: struct `enum_union_disabled_copy_variant.DisabledPostBlit` is not copyable because it has a disabled postblit +--- +*/ + +struct DisabledCopy +{ + int x; + @disable this(ref DisabledCopy); + this(int v) { x = v; } +} + +enum union HasDisabledCopy +{ + case Wrapped(DisabledCopy); + case Flag(bool); +} + +struct DisabledPostBlit +{ + int x; + @disable this(this); +} + +enum union HasDisabledPostBlit +{ + case Wrapped(DisabledPostBlit); + case Flag(bool); +} diff --git a/compiler/test/fail_compilation/enum_union_duplicate_case.d b/compiler/test/fail_compilation/enum_union_duplicate_case.d new file mode 100644 index 000000000000..9777a36efe13 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_duplicate_case.d @@ -0,0 +1,55 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_duplicate_case.d(16): Error: duplicate case `StructVariant` in enum union `enum_union_duplicate_case.Test1` +fail_compilation/enum_union_duplicate_case.d(23): Error: duplicate case `ExternalStruct` in enum union `enum_union_duplicate_case.Test2` +fail_compilation/enum_union_duplicate_case.d(30): Error: duplicate case `ExternalStruct` in enum union `enum_union_duplicate_case.Test3` +fail_compilation/enum_union_duplicate_case.d(37): Error: duplicate case `StructVariant` in enum union `enum_union_duplicate_case.Test4` +fail_compilation/enum_union_duplicate_case.d(44): Error: duplicate case `Variant1` in enum union `enum_union_duplicate_case.Test5` +fail_compilation/enum_union_duplicate_case.d(51): Error: duplicate case `double` in enum union `enum_union_duplicate_case.Test6` +--- +*/ + +struct ExternalStruct {} + +// 1. Two record variants sharing the same name and identical bodies +enum union Test1 +{ + case StructVariant {}; + case StructVariant {}; +} + +// 2. Bare type vs positional variant with the same name +enum union Test2 +{ + case ExternalStruct; + case ExternalStruct(int, string); +} + +// 3. Bare type vs unit variant with the same name +enum union Test3 +{ + case ExternalStruct; + case ExternalStruct(); +} + +// 4. Same variant name reused with different record fields +enum union Test4 +{ + case StructVariant { int id; }; + case StructVariant { string s; }; +} + +// 5. Same name reused across different variant kinds (record vs positional) +enum union Test5 +{ + case Variant1 { int id; }; + case Variant1(int); +} + +// 6. Duplicate bare type +enum union Test6 +{ + case double; + case double; +} diff --git a/compiler/test/fail_compilation/enum_union_duplicate_pattern_binding.d b/compiler/test/fail_compilation/enum_union_duplicate_pattern_binding.d new file mode 100644 index 000000000000..48954eecc562 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_duplicate_pattern_binding.d @@ -0,0 +1,21 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_duplicate_pattern_binding.d(18): Error: duplicate pattern binding `item` +--- +*/ + +enum union Value +{ + case Pair(int, int); + case None(); +} + +int inspect(Value value) +{ + return switch (value) + { + case Pair(item, item) => item, + case None() => 0, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_legacy_template_payload.d b/compiler/test/fail_compilation/enum_union_legacy_template_payload.d new file mode 100644 index 000000000000..bb22dde10378 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_legacy_template_payload.d @@ -0,0 +1,16 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_legacy_template_payload.d(14): Error: function declaration without return type +fail_compilation/enum_union_legacy_template_payload.d(14): Note that constructors are always named `this` +fail_compilation/enum_union_legacy_template_payload.d(14): Error: found `*` when expecting `)` +fail_compilation/enum_union_legacy_template_payload.d(14): Error: `,` or `;` expected after enum union variant +fail_compilation/enum_union_legacy_template_payload.d(14): Error: declaration expected, not `)` +--- +*/ + +enum union List(T) +{ + case Cons(T, List(T)*); + case Nil(); +} diff --git a/compiler/test/fail_compilation/enum_union_move_only_variant.d b/compiler/test/fail_compilation/enum_union_move_only_variant.d new file mode 100644 index 000000000000..fd9a81ea201a --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_move_only_variant.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_move_only_variant.d(18): Error: cannot create enum union with element type `MoveOnly` that has a move constructor but no copy constructor +--- +*/ + +// A payload type with a move constructor but no copy constructor cannot be +// stored in an enum union: ordinary copies of the enum union (assignment, +// pass-by-value) would raw-bitcopy the payload union instead of invoking the +// move constructor, leading to double-destruction of the payload. +struct MoveOnly +{ + int x; + this(return MoveOnly other) { x = other.x; } +} + +enum union WithMoveOnly +{ + case Moved(MoveOnly); + case Other(bool); +} diff --git a/compiler/test/fail_compilation/enum_union_noarg_ctor.d b/compiler/test/fail_compilation/enum_union_noarg_ctor.d new file mode 100644 index 000000000000..2a93adeb6503 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_noarg_ctor.d @@ -0,0 +1,21 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_noarg_ctor.d(13): Error: enum union `enum_union_noarg_ctor.NoArgCtor` cannot have a no-argument constructor; use `.init` instead +fail_compilation/enum_union_noarg_ctor.d(21): Error: enum union `enum_union_noarg_ctor.NoArgCall` cannot be constructed with no arguments; use `.init` instead +--- +*/ + +enum union NoArgCtor +{ + case Number(int); + + this() {} +} + +enum union NoArgCall +{ + case Number(int); +} + +auto val = NoArgCall(); diff --git a/compiler/test/fail_compilation/enum_union_record_variant_recovery.d b/compiler/test/fail_compilation/enum_union_record_variant_recovery.d new file mode 100644 index 000000000000..7e37151de45b --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_record_variant_recovery.d @@ -0,0 +1,15 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_record_variant_recovery.d(11): Error: semicolon needed to end declaration of `List` instead of `!` +fail_compilation/enum_union_record_variant_recovery.d(11): Error: declaration expected, not `!` +--- +*/ + +enum union List(T) +{ + case Cons { T value, List!T* tail }; + case typeof(null); +} + +void main() {} diff --git a/compiler/test/fail_compilation/enum_union_shorthand_lambda_no_infer.d b/compiler/test/fail_compilation/enum_union_shorthand_lambda_no_infer.d new file mode 100644 index 000000000000..ab511f48853f --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_shorthand_lambda_no_infer.d @@ -0,0 +1,24 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_shorthand_lambda_no_infer.d(23): Error: `enum_union_shorthand_lambda_no_infer.Funs.__ctor` called with argument types `((n) => n)` matches multiple overloads after qualifier conversion: +fail_compilation/enum_union_shorthand_lambda_no_infer.d(15): `enum_union_shorthand_lambda_no_infer.Funs.this(int delegate(int) __enumPayloadParam12)` +and: +fail_compilation/enum_union_shorthand_lambda_no_infer.d(15): `enum_union_shorthand_lambda_no_infer.Funs.this(string function(string) __enumPayloadParam13)` +--- +*/ + +// The shorthand single-param lambda syntax (`n => n`) cannot infer `n`'s +// type here: the assignment target is the enum union struct itself with +// ambiguous parameter types (`int` vs `string`), so there is no unique +// parameter type for the compiler to infer from. +enum union Funs +{ + case int delegate(int); + case string function(string); +} + +void test() +{ + Funs f = n => n; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_default_guard.d b/compiler/test/fail_compilation/enum_union_switch_default_guard.d new file mode 100644 index 000000000000..ef5fe8116c11 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_default_guard.d @@ -0,0 +1,21 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_default_guard.d(19): Error: `default` arm cannot have an `if` guard +--- +*/ + +enum union Shape +{ + case Circle(double); + case Point(); +} + +string guardedDefault(Shape s) +{ + return switch (s) + { + case Circle(r) => "circle", + default if (true) => "point", + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_duplicate_default.d b/compiler/test/fail_compilation/enum_union_switch_duplicate_default.d new file mode 100644 index 000000000000..cb66b9357ccc --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_duplicate_default.d @@ -0,0 +1,21 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_duplicate_default.d(19): Error: duplicate `default` arm in switch expression +--- +*/ + +enum union E +{ + case A(); +} + +int test(E value) +{ + return switch (value) + { + case A() => 0, + default => 1, + default => 2, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_expression_payload_pattern.d b/compiler/test/fail_compilation/enum_union_switch_expression_payload_pattern.d new file mode 100644 index 000000000000..b287ec877855 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_expression_payload_pattern.d @@ -0,0 +1,34 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_expression_payload_pattern.d(21): Error: value tests in patterns are not supported; bind the value and use an `if` guard +fail_compilation/enum_union_switch_expression_payload_pattern.d(31): Error: value tests in patterns are not supported; bind the value and use an `if` guard +--- +*/ + +enum union Value +{ + case Number(int); + case Point { int x; int y; }; +} + +int nextValue(); + +int inspectNumber(Value value) +{ + return switch (value) + { + case Number(nextValue()) => 1, + default => 0, + }; +} + +int inspectPoint(Value value) +{ + int expected; + return switch (value) + { + case Point { x: expected + 1, y } => y, + default => 0, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_non_union.d b/compiler/test/fail_compilation/enum_union_switch_non_union.d new file mode 100644 index 000000000000..efd5b03a8ece --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_non_union.d @@ -0,0 +1,15 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_non_union.d(12): Error: switch expression patterns require an enum union condition +--- +*/ + +int test() +{ + return switch (1) + { + case value => 0, + default => 1, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_not_exhaustive.d b/compiler/test/fail_compilation/enum_union_switch_not_exhaustive.d new file mode 100644 index 000000000000..e9b643e207b3 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_not_exhaustive.d @@ -0,0 +1,62 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_not_exhaustive.d(20): Error: switch expression is not exhaustive; missing pattern `Point` +fail_compilation/enum_union_switch_not_exhaustive.d(29): Error: switch expression is not exhaustive; missing pattern `Circle(_)` +fail_compilation/enum_union_switch_not_exhaustive.d(44): Error: switch expression is not exhaustive; missing pattern `Square(_, _)` +fail_compilation/enum_union_switch_not_exhaustive.d(58): Error: switch expression is not exhaustive; missing pattern `typeof(null)` +--- +*/ + +enum union Shape +{ + case Circle(double); + case Rectangle(double, double); + case Point(); +} + +string testMissingVariant(Shape s) +{ + return switch (s) + { + case Circle(r) => "circle", + case Rectangle(w, h) => "rectangle", + }; +} + +string testGuardedShape(Shape s) +{ + return switch (s) + { + case Circle(r) if (r > 0.0) => "circle", + case Rectangle(w, h) => "rectangle", + case Point() => "point", + }; +} + +enum union Rect +{ + case Square(int height, int width); +} + +int testGuardedMultiField(Rect r) +{ + return switch (r) + { + case Square(height, width) if (height > 0) => 1, + }; +} + +enum union Value +{ + case Number(int); + case typeof(null); +} + +int testMissingNullVariant(Value v) +{ + return switch (v) + { + case Number(value) => value, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_positional_arity.d b/compiler/test/fail_compilation/enum_union_switch_positional_arity.d new file mode 100644 index 000000000000..2f101fc33879 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_positional_arity.d @@ -0,0 +1,31 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_positional_arity.d(19): Error: pattern for variant `Pair` has 1 argument(s), expected 2 +fail_compilation/enum_union_switch_positional_arity.d(28): Error: pattern for variant `Pair` has 3 argument(s), expected 2 +--- +*/ + +enum union E +{ + case Pair(int, int); + case Done(); +} + +int testFew(E value) +{ + return switch (value) + { + case Pair(1) => 0, + default => 0, + }; +} + +int testExtra(E value) +{ + return switch (value) + { + case Pair(left, right, extra) => left + right, + default => 0, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_positional_value_pattern.d b/compiler/test/fail_compilation/enum_union_switch_positional_value_pattern.d new file mode 100644 index 000000000000..10eaab36f590 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_positional_value_pattern.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_positional_value_pattern.d(18): Error: value tests in patterns are not supported; bind the value and use an `if` guard +--- +*/ + +enum union Value +{ + case Number(int); + case Empty(); +} + +int inspect(Value value) +{ + return switch (value) + { + case Number(42) => 1, + case Number(number) => number, + case Empty() => 0, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_record_value_pattern.d b/compiler/test/fail_compilation/enum_union_switch_record_value_pattern.d new file mode 100644 index 000000000000..e6070a3d5667 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_record_value_pattern.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_record_value_pattern.d(18): Error: value tests in patterns are not supported; bind the value and use an `if` guard +--- +*/ + +enum union Value +{ + case Point { int x; int y; }; + case Empty(); +} + +int inspect(Value value) +{ + return switch (value) + { + case Point { x: 42, y } => y, + case Point { x, y } => x + y, + case Empty() => 0, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_redundant.d b/compiler/test/fail_compilation/enum_union_switch_redundant.d new file mode 100644 index 000000000000..3e125a603b87 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_redundant.d @@ -0,0 +1,54 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_redundant.d(22): Error: redundant match arm; pattern is unreachable +fail_compilation/enum_union_switch_redundant.d(31): Error: redundant match arm; pattern is unreachable +fail_compilation/enum_union_switch_redundant.d(42): Error: redundant match arm; pattern is unreachable +fail_compilation/enum_union_switch_redundant.d(52): Error: redundant match arm; pattern is unreachable +--- +*/ + +enum union Shape +{ + case Circle(double); + case Point(); +} + +string testArmAfterDefault(Shape shape) +{ + return switch (shape) + { + default => "other", + case Circle(radius) => "circle", + }; +} + +int testConstrainedAfterUnconstrained(Shape shape) +{ + return switch (shape) + { + case Circle(radius) => 1, + case Circle(radius) if (radius > 10) => 2, + default => 0, + }; +} + +string testDuplicateArm(Shape shape) +{ + return switch (shape) + { + case Circle(r) => "circle", + case Point() => "point", + case Point() => "point again", + }; +} + +string testDefaultAfterExhaustive(Shape shape) +{ + return switch (shape) + { + case Circle(radius) => "circle", + case Point() => "point", + default => "unreachable", + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_static_foreach.d b/compiler/test/fail_compilation/enum_union_switch_static_foreach.d new file mode 100644 index 000000000000..5e1ad7019873 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_static_foreach.d @@ -0,0 +1,37 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_static_foreach.d(24): Error: redundant match arm; pattern is unreachable +fail_compilation/enum_union_switch_static_foreach.d(31): Error: switch expression is not exhaustive; missing pattern `double` +--- +*/ + +template AliasSeq(T...) { alias AliasSeq = T; } + +enum union Number +{ + case error(); + case int; + case double; +} + +int testDup(Number a) +{ + return switch (a) + { + case error() => 0, + static foreach (T; AliasSeq!(int, int)) + case T i => i, + case double d => cast(int)d, + }; +} + +int testNonExhaustive(Number a) +{ + return switch (a) + { + case error() => 0, + static foreach (T; AliasSeq!(int)) + case T i => i, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_unexpected_integer_pattern.d b/compiler/test/fail_compilation/enum_union_switch_unexpected_integer_pattern.d new file mode 100644 index 000000000000..9ca789badf34 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_unexpected_integer_pattern.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_unexpected_integer_pattern.d(18): Error: switch expression value and expression patterns are not supported; use a named variant or type pattern +--- +*/ + +enum union Value +{ + case int; + case string; +} + +string classify(Value value) +{ + return switch (value) + { + case 0 => "zero", + case int => "integer", + case string => "string", + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_unexpected_null_pattern.d b/compiler/test/fail_compilation/enum_union_switch_unexpected_null_pattern.d new file mode 100644 index 000000000000..728471846b9c --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_unexpected_null_pattern.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_unexpected_null_pattern.d(18): Error: switch expression value and expression patterns are not supported; use a named variant or type pattern +--- +*/ + +enum union Pointers +{ + case int*; + case bool*; +} + +string classify(Pointers pointers) +{ + return switch (pointers) + { + case null => "null", + case int* => "integer", + case bool* => "boolean", + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_unmatched_pattern.d b/compiler/test/fail_compilation/enum_union_switch_unmatched_pattern.d new file mode 100644 index 000000000000..6c21c2655715 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_unmatched_pattern.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_unmatched_pattern.d(18): Error: switch expression call pattern `Square` does not name a variant of `enum_union_switch_unmatched_pattern.Shape` +--- +*/ + +enum union Shape +{ + case Circle(double); + case Point(); +} + +string describe(Shape s) +{ + return switch (s) + { + case Square(r) => "square", // no such variant + case Point() => "point", + default => "circle", + }; +} diff --git a/compiler/test/fail_compilation/enum_union_switch_unnamed_call_pattern.d b/compiler/test/fail_compilation/enum_union_switch_unnamed_call_pattern.d new file mode 100644 index 000000000000..1c3c313919c0 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_switch_unnamed_call_pattern.d @@ -0,0 +1,20 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_switch_unnamed_call_pattern.d(17): Error: switch expression call pattern requires a named variant callee +--- +*/ + +enum union Value +{ + case int; +} + +int classify(Value value) +{ + return switch (value) + { + case (0)(1) => 0, + case int => 1, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_too_many_variants.d b/compiler/test/fail_compilation/enum_union_too_many_variants.d new file mode 100644 index 000000000000..b8e884c9b7a3 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_too_many_variants.d @@ -0,0 +1,43 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_too_many_variants.d(8): Error: enum union cannot have more than 256 variants +--- +*/ + +enum union TooMany +{ + case V0(), V1(), V2(), V3(), V4(), V5(), V6(), V7(); + case V8(), V9(), V10(), V11(), V12(), V13(), V14(), V15(); + case V16(), V17(), V18(), V19(), V20(), V21(), V22(), V23(); + case V24(), V25(), V26(), V27(), V28(), V29(), V30(), V31(); + case V32(), V33(), V34(), V35(), V36(), V37(), V38(), V39(); + case V40(), V41(), V42(), V43(), V44(), V45(), V46(), V47(); + case V48(), V49(), V50(), V51(), V52(), V53(), V54(), V55(); + case V56(), V57(), V58(), V59(), V60(), V61(), V62(), V63(); + case V64(), V65(), V66(), V67(), V68(), V69(), V70(), V71(); + case V72(), V73(), V74(), V75(), V76(), V77(), V78(), V79(); + case V80(), V81(), V82(), V83(), V84(), V85(), V86(), V87(); + case V88(), V89(), V90(), V91(), V92(), V93(), V94(), V95(); + case V96(), V97(), V98(), V99(), V100(), V101(), V102(), V103(); + case V104(), V105(), V106(), V107(), V108(), V109(), V110(), V111(); + case V112(), V113(), V114(), V115(), V116(), V117(), V118(), V119(); + case V120(), V121(), V122(), V123(), V124(), V125(), V126(), V127(); + case V128(), V129(), V130(), V131(), V132(), V133(), V134(), V135(); + case V136(), V137(), V138(), V139(), V140(), V141(), V142(), V143(); + case V144(), V145(), V146(), V147(), V148(), V149(), V150(), V151(); + case V152(), V153(), V154(), V155(), V156(), V157(), V158(), V159(); + case V160(), V161(), V162(), V163(), V164(), V165(), V166(), V167(); + case V168(), V169(), V170(), V171(), V172(), V173(), V174(), V175(); + case V176(), V177(), V178(), V179(), V180(), V181(), V182(), V183(); + case V184(), V185(), V186(), V187(), V188(), V189(), V190(), V191(); + case V192(), V193(), V194(), V195(), V196(), V197(), V198(), V199(); + case V200(), V201(), V202(), V203(), V204(), V205(), V206(), V207(); + case V208(), V209(), V210(), V211(), V212(), V213(), V214(), V215(); + case V216(), V217(), V218(), V219(), V220(), V221(), V222(), V223(); + case V224(), V225(), V226(), V227(), V228(), V229(), V230(), V231(); + case V232(), V233(), V234(), V235(), V236(), V237(), V238(), V239(); + case V240(), V241(), V242(), V243(), V244(), V245(), V246(), V247(); + case V248(), V249(), V250(), V251(), V252(), V253(), V254(), V255(); + case V256(); +} diff --git a/compiler/test/fail_compilation/enum_union_tuple_pattern_arity.d b/compiler/test/fail_compilation/enum_union_tuple_pattern_arity.d new file mode 100644 index 000000000000..1732b95b1282 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_tuple_pattern_arity.d @@ -0,0 +1,26 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_tuple_pattern_arity.d(23): Error: incompatible number of components for unpack declaration (`3` vs. `2`) +--- +*/ + +struct Tuple(T...) +{ + T expand; + alias expand this; +} + +enum union Value +{ + case Wrapped(Tuple!(int, int)); +} + +int inspect(Value value) +{ + return switch (value) + { + case Wrapped((first, second, third)) => first + second + third, + default => 0, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_tuple_pattern_value.d b/compiler/test/fail_compilation/enum_union_tuple_pattern_value.d new file mode 100644 index 000000000000..2f3ee7a875ff --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_tuple_pattern_value.d @@ -0,0 +1,26 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_tuple_pattern_value.d(23): Error: value tests in tuple patterns are not supported; bind the value and use an `if` guard +--- +*/ + +struct Tuple(T...) +{ + T expand; + alias expand this; +} + +enum union Value +{ + case Wrapped(Tuple!(int, int)); +} + +int inspect(Value value) +{ + return switch (value) + { + case Wrapped((1, right)) => right, + default => 0, + }; +} diff --git a/compiler/test/fail_compilation/enum_union_unit_pattern_post_call_binding.d b/compiler/test/fail_compilation/enum_union_unit_pattern_post_call_binding.d new file mode 100644 index 000000000000..04e90bdda05e --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_unit_pattern_post_call_binding.d @@ -0,0 +1,16 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_unit_pattern_post_call_binding.d(15): Error: `=>` expected in switch expression arm +--- +*/ + +enum union Value +{ + case Unit(); +} + +auto value = switch (Value.Unit) +{ + case Unit() binding => 0, +}; diff --git a/compiler/test/fail_compilation/enum_union_unknown_bare_type.d b/compiler/test/fail_compilation/enum_union_unknown_bare_type.d new file mode 100644 index 000000000000..9f1a1a2070e9 --- /dev/null +++ b/compiler/test/fail_compilation/enum_union_unknown_bare_type.d @@ -0,0 +1,11 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/enum_union_unknown_bare_type.d(10): Error: unknown type `ExternalStruct`; for a named unit variant, use `case ExternalStruct()` +--- +*/ + +enum union Test +{ + case ExternalStruct; +} diff --git a/compiler/test/fail_compilation/switch_expression_missing_semicolon.d b/compiler/test/fail_compilation/switch_expression_missing_semicolon.d new file mode 100644 index 000000000000..acbf0e0338d0 --- /dev/null +++ b/compiler/test/fail_compilation/switch_expression_missing_semicolon.d @@ -0,0 +1,21 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/switch_expression_missing_semicolon.d(19): Error: found `}` when expecting `;` following expression +--- +*/ + +enum union Value +{ + case Unit(); +} + +void test() +{ + cast(void)switch (Value.Unit) + { + case Unit => 0, + } +} + +private int shouldNotCauseACascade; diff --git a/compiler/test/fail_compilation/switch_expression_statement_no_effect.d b/compiler/test/fail_compilation/switch_expression_statement_no_effect.d new file mode 100644 index 000000000000..43c3d9e756d5 --- /dev/null +++ b/compiler/test/fail_compilation/switch_expression_statement_no_effect.d @@ -0,0 +1,21 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/switch_expression_statement_no_effect.d(16): Error: switch expression has no effect; use `cast(void)` to discard its value +--- +*/ + +enum union Value +{ + case Unit(); + case Number(int); +} + +void main() +{ + switch (Value.Unit) + { + case Unit value => "unit", + case Number value => "number", + } +} diff --git a/compiler/test/runnable/enum_union_conversion_ranking.d b/compiler/test/runnable/enum_union_conversion_ranking.d new file mode 100644 index 000000000000..17276a9b8642 --- /dev/null +++ b/compiler/test/runnable/enum_union_conversion_ranking.d @@ -0,0 +1,54 @@ +module enum_union_conversion_ranking; + +enum union ConfigValue +{ + case bool; + case long; + case double; + case string; + case string[]; +} + +struct Empty +{ +} + +enum union WithUnitVariant +{ + case None(); + case int; +} + +void main() +{ + WithUnitVariant unit = WithUnitVariant.None; + assert(unit.__tag == 0); + static assert(!__traits(compiles, (() { + WithUnitVariant unrelated = Empty(); + })())); + + ConfigValue literalBool = false; + assert(literalBool.__tag == 0); + + ConfigValue castBool = cast(bool) false; + assert(castBool.__tag == 0); + + ConfigValue constructedBool = bool(false); + assert(constructedBool.__tag == 0); + + bool boolValue; + ConfigValue typedBool = boolValue; + assert(typedBool.__tag == 0); + + ConfigValue longValue = long(42); + assert(longValue.__tag == 1); + + ConfigValue doubleValue = 42.0; + assert(doubleValue.__tag == 2); + + ConfigValue stringValue = "value"; + assert(stringValue.__tag == 3); + + ConfigValue stringsValue = ["first", "second"]; + assert(stringsValue.__tag == 4); +} diff --git a/compiler/test/runnable/enum_union_copy_lifecycle.d b/compiler/test/runnable/enum_union_copy_lifecycle.d new file mode 100644 index 000000000000..f4bfe7e2c4c1 --- /dev/null +++ b/compiler/test/runnable/enum_union_copy_lifecycle.d @@ -0,0 +1,53 @@ +struct Resource +{ + int id; + static int liveCount = 0; + static int copyCount = 0; + + this(int id) + { + this.id = id; + liveCount++; + } + + this(ref return scope typeof(this) rhs) + { + this.id = rhs.id; + liveCount++; + copyCount++; + } + + ~this() + { + liveCount--; + } +} + +enum union Holder +{ + case Item(Resource); + case Empty(); +} + +void passByValue(Holder h) +{ + assert(Resource.liveCount == 2); +} + +void main() +{ + { + Holder h1 = Holder.Item(Resource(42)); + assert(Resource.liveCount == 1); + assert(Resource.copyCount == 1); // 1 copy into the factory function payload + + passByValue(h1); + assert(Resource.liveCount == 1); + assert(Resource.copyCount == 2); // 1 copy for passByValue + + Holder h2 = h1; + assert(Resource.liveCount == 2); + assert(Resource.copyCount == 3); // 1 copy for h2 = h1 + } + assert(Resource.liveCount == 0); // Both instances cleanly destructed +} diff --git a/compiler/test/runnable/enum_union_guard_value_tests.d b/compiler/test/runnable/enum_union_guard_value_tests.d new file mode 100644 index 000000000000..ff4078ac7b05 --- /dev/null +++ b/compiler/test/runnable/enum_union_guard_value_tests.d @@ -0,0 +1,55 @@ +enum Color +{ + red, + blue, +} + +class Reference +{ +} + +struct Comparable +{ + int value; + + bool opEquals(const Comparable other) const + { + return value == other.value; + } +} + +enum union Value +{ + case Number(int); + case Text(string); + case ColorValue(Color); + case ArrayValue(int[]); + case ReferenceValue(Reference); + case ComparableValue(Comparable); +} + +bool matches(Value value, Reference expectedReference) +{ + return switch (value) + { + case Number(number) if (number == 42) => true, + case Text(text) if (text == "answer") => true, + case ColorValue(color) if (color == Color.blue) => true, + case ArrayValue(items) if (items == [1, 2, 3]) => true, + case ReferenceValue(reference) if (reference is expectedReference) => true, + case ComparableValue(item) if (item == Comparable(42)) => true, + default => false, + }; +} + +void main() +{ + auto reference = new Reference(); + assert(matches(Value.Number(42), reference)); + assert(matches(Value.Text("answer"), reference)); + assert(matches(Value.ColorValue(Color.blue), reference)); + assert(matches(Value.ArrayValue([1, 2, 3]), reference)); + assert(matches(Value.ReferenceValue(reference), reference)); + assert(matches(Value.ComparableValue(Comparable(42)), reference)); + assert(!matches(Value.Number(7), reference)); +} diff --git a/compiler/test/runnable/enum_union_scoping.d b/compiler/test/runnable/enum_union_scoping.d new file mode 100644 index 000000000000..8f7fbe77aa69 --- /dev/null +++ b/compiler/test/runnable/enum_union_scoping.d @@ -0,0 +1,42 @@ +module enum_union_scoping; + +// Test member function inside a local enum union +void testLocalMemberFactory() +{ + enum union LocalPacket + { + case Data(int); + + int value() { return 42; } + + case Ping(long); + } + + assert(LocalPacket.Data(1).value() == 42); + assert(LocalPacket.Ping(2).value() == 42); +} + +// Test alias variant inside with-statement +alias None = typeof(null); + +enum union Option(T) +{ + case None = typeof(null); + case Some = T; +} + +void testAliasVariantWith() +{ + Option!string qualified = Option!string.Some("asdf"); + with (Option!string) + { + Option!string unqualified = Some("asdf"); + assert(unqualified.__tag == qualified.__tag); + } +} + +void main() +{ + testLocalMemberFactory(); + testAliasVariantWith(); +} diff --git a/compiler/test/runnable/enum_union_switch_statement_side_effects.d b/compiler/test/runnable/enum_union_switch_statement_side_effects.d new file mode 100644 index 000000000000..d1c10206b3b0 --- /dev/null +++ b/compiler/test/runnable/enum_union_switch_statement_side_effects.d @@ -0,0 +1,65 @@ +int factoryArgumentCalls; +int guardCalls; +int actionCalls; +int bindingCopyCalls; + +int nextNumber() +{ + factoryArgumentCalls++; + return 42; +} + +bool guardEffect() +{ + guardCalls++; + return true; +} + +int actionEffect() +{ + actionCalls++; + return 1; +} + +struct CopyCounted +{ + this(ref CopyCounted) + { + bindingCopyCalls++; + } +} + +enum union Value +{ + case Number(int); + case Wrapped(CopyCounted); + case Empty(); +} + +void main() +{ + switch (Value.Number(nextNumber())) + { + case Number(number) => number, + case Wrapped(payload) => 0, + case Empty() => 0, + } + assert(factoryArgumentCalls == 1); + + switch (Value.Number(1)) + { + case Number(number) if (guardEffect()) => actionEffect(), + default => 0, + } + assert(guardCalls == 1); + assert(actionCalls == 1); + + Value wrapped = Value.Wrapped(CopyCounted()); + bindingCopyCalls = 0; + switch (wrapped) + { + case Wrapped(payload) => 0, + default => 0, + } + assert(bindingCopyCalls == 1); +} diff --git a/compiler/test/runnable/enum_union_throwing_construction.d b/compiler/test/runnable/enum_union_throwing_construction.d new file mode 100644 index 000000000000..df56817c5364 --- /dev/null +++ b/compiler/test/runnable/enum_union_throwing_construction.d @@ -0,0 +1,56 @@ +int invalidDestructions; +int copyAttempts; + +struct ThrowingPayload +{ + bool initialized; + + this(int) + { + initialized = true; + } + + this(ref ThrowingPayload) + { + copyAttempts++; + throw new Exception("copy failed"); + } + + ~this() + { + if (!initialized) + invalidDestructions++; + } +} + +enum union NamedHolder +{ + case Item(ThrowingPayload); + case Empty(); +} + +enum union BareHolder +{ + case ThrowingPayload; + case int; +} + +void main() +{ + try + auto value = NamedHolder.Item(ThrowingPayload(1)); + catch (Exception) + { + } + assert(copyAttempts == 1); + assert(invalidDestructions == 0); + + ThrowingPayload payload = ThrowingPayload(2); + try + BareHolder value = payload; + catch (Exception) + { + } + assert(copyAttempts == 2); + assert(invalidDestructions == 0); +} diff --git a/compiler/test/runnable/enum_union_traits.d b/compiler/test/runnable/enum_union_traits.d new file mode 100644 index 000000000000..bb349b3b6c26 --- /dev/null +++ b/compiler/test/runnable/enum_union_traits.d @@ -0,0 +1,179 @@ +module enum_union_traits; + +struct CustomPayload +{ + int id; +} + +enum union Message +{ + @("tag_none") case None(), + case Move(int x, int y), + @("tag_user") case User { int id; string name; }, + case CustomPayload, + case int, + @("tag_slice") case Slice = ubyte[], + case Payload = CustomPayload, +} + +void main() +{ + struct LocalPayload + { + int value; + } + + enum union LocalMessage + { + case None(), + case Payload = LocalPayload, + } + + static foreach (index, Variant; __traits(allVariants, LocalMessage)) + { + static if (index == 1) + { + static assert(__traits(variantParams, Variant).length == 0); + static assert(__traits(variantParamNames, Variant).length == 0); + } + } + + static assert(is(Message == enum union)); + static assert(!is(int == enum union)); + static assert(!is(CustomPayload == enum union)); + + alias Variants = __traits(allVariants, Message); + static assert(Variants.length == 7); + static assert(is(Message.User == struct)); + static assert(is(Message.Slice == ubyte[])); + static assert(__traits(isSame, Variants[2], + __traits(getVariant, Message, "User"))); + + static assert(__traits(variantTag, Variants[0]) == 0); + static assert(__traits(variantTag, Variants[1]) == 1); + static assert(__traits(variantTag, Variants[2]) == 2); + static assert(__traits(variantTag, Variants[3]) == 3); + static assert(__traits(variantTag, Variants[4]) == 4); + static assert(__traits(variantTag, Variants[5]) == 5); + static assert(__traits(variantTag, Variants[6]) == 6); + static assert(is(typeof(__traits(variantTag, Variants[0])) == ubyte)); + + static assert(__traits(variantKind, Variants[0]) == "unit"); + static assert(__traits(variantKind, Variants[1]) == "tuple"); + static assert(__traits(variantKind, Variants[2]) == "struct"); + static assert(__traits(variantKind, Variants[3]) == "bare"); + static assert(__traits(variantKind, Variants[4]) == "bare"); + static assert(__traits(variantKind, Variants[5]) == "alias"); + static assert(__traits(variantKind, Variants[6]) == "alias"); + + static assert(__traits(variantParams, Variants[0]).length == 0); + static assert(is(__traits(variantParams, Variants[1]) == AliasSeq!(int, int))); + static assert(is(__traits(variantParams, Variants[2]) == AliasSeq!(int, string))); + static assert(__traits(variantParams, Variants[3]).length == 0); + static assert(__traits(variantParams, Variants[4]).length == 0); + static assert(__traits(variantParams, Variants[5]).length == 0); + static assert(__traits(variantParams, Variants[6]).length == 0); + + static assert(__traits(variantParamNames, Variants[0]).length == 0); + static assert(__traits(variantParamNames, Variants[1]) == AliasSeq!("x", "y")); + static assert(__traits(variantParamNames, Variants[2]) == AliasSeq!("id", "name")); + static assert(__traits(variantParamNames, Variants[3]).length == 0); + static assert(__traits(variantParamNames, Variants[4]).length == 0); + static assert(__traits(variantParamNames, Variants[5]).length == 0); + static assert(__traits(variantParamNames, Variants[6]).length == 0); + + static foreach (Variant; Variants) + { + static assert(__traits(compiles, __traits(variantParams, Variant))); + static assert(__traits(variantParams, Variant).length == + __traits(variantParamNames, Variant).length); + } + + static assert(__traits(identifier, Variants[0]) == "None"); + static assert(__traits(identifier, Variants[1]) == "Move"); + static assert(__traits(identifier, Variants[2]) == "User"); + static assert(__traits(identifier, Variants[3]) == "CustomPayload"); + static assert(__traits(identifier, Variants[4]) == ""); + static assert(__traits(identifier, Variants[5]) == "Slice"); + static assert(__traits(identifier, Variants[6]) == "Payload"); + + alias NoneAttrs = __traits(getAttributes, Variants[0]); + static assert(NoneAttrs.length == 1 && NoneAttrs[0] == "tag_none"); + alias UserAttrs = __traits(getAttributes, Variants[2]); + static assert(UserAttrs.length == 1 && UserAttrs[0] == "tag_user"); + alias SliceAttrs = __traits(getAttributes, Variants[5]); + static assert(SliceAttrs.length == 1 && SliceAttrs[0] == "tag_slice"); + + Message.User user = Message.User(7, "Ada"); + Message userMessage = user; + assert(userMessage.__tag == __traits(variantTag, Variants[2])); + const constMessage = userMessage; + immutable immutableMessage = Message.None(); + shared Message sharedMessage; + assert(constMessage.__tag == __traits(variantTag, Variants[2])); + assert(immutableMessage.__tag == __traits(variantTag, Variants[0])); + assert(sharedMessage.__tag == __traits(variantTag, Variants[0])); + static assert(__traits(hasMember, Message, "__tag")); + assert(__traits(getMember, userMessage, "__tag") == + __traits(variantTag, Variants[2])); + static assert(CountTag!(__traits(allMembers, Message)) == 1); + static foreach (member; Message.tupleof) + static assert(__traits(identifier, member) != "__tag"); + static assert(!__traits(compiles, (ref Message value) { value.__tag = 1; })); + static assert(!__traits(compiles, (ref Message value) { value.__tag += 1; })); + static assert(!__traits(compiles, (ref Message value) { value.__tag++; })); + static assert(!__traits(compiles, (ref Message value) { auto pointer = &value.__tag; })); + static assert(!__traits(compiles, (ref Message value) { ref ubyte tag = value.__tag; })); + Message.Slice bytes = [ubyte(1), 2, 3]; + Message bytesMessage = bytes; + assert(bytesMessage.__tag == __traits(variantTag, Variants[5])); + + static assert(!__traits(compiles, __traits(allVariants, int))); + static assert(!__traits(compiles, __traits(getTag, Message, Variants[0]))); + static assert(!__traits(compiles, __traits(variantTag, double))); + static assert(!__traits(compiles, __traits(variantTag, Message, Variants[0]))); + static assert(!__traits(compiles, __traits(variantKind, main))); + static assert(!__traits(compiles, __traits(variantParams, main))); + static assert(!__traits(compiles, __traits(variantParamNames, main))); + + // hasVariant and getVariant tests + static assert(__traits(hasVariant, Message, "None")); + static assert(__traits(hasVariant, Message, "Move")); + static assert(__traits(hasVariant, Message, "User")); + static assert(__traits(hasVariant, Message, "Slice")); + static assert(__traits(hasVariant, Message, int)); + static assert(!__traits(hasVariant, Message, "missing")); + static assert(!__traits(hasVariant, Message, double)); + + static assert(__traits(isSame, __traits(getVariant, Message, "None"), Variants[0])); + static assert(__traits(isSame, __traits(getVariant, Message, "Move"), Variants[1])); + static assert(__traits(isSame, __traits(getVariant, Message, "User"), Variants[2])); + static assert(__traits(isSame, __traits(getVariant, Message, "Slice"), Variants[5])); + static assert(__traits(isSame, __traits(getVariant, Message, int), Variants[4])); + + // Partially-named tuple variant parameter traits + enum union Shape + { + case Arc(double, double, double radius); + } + alias ShapeVars = __traits(allVariants, Shape); + static assert(is(__traits(variantParams, ShapeVars[0]) == AliasSeq!(double, double, double))); + static assert(__traits(variantParamNames, ShapeVars[0]) == AliasSeq!("", "", "radius")); +} + +template AliasSeq(T...) +{ + alias AliasSeq = T; +} + +template CountTag(names...) +{ + enum CountTag = () + { + size_t count; + static foreach (name; names) + if (name == "__tag") + count++; + return count; + }(); +} diff --git a/compiler/test/runnable/enum_union_tuple_patterns.d b/compiler/test/runnable/enum_union_tuple_patterns.d new file mode 100644 index 000000000000..bc77f4ada442 --- /dev/null +++ b/compiler/test/runnable/enum_union_tuple_patterns.d @@ -0,0 +1,64 @@ +struct Tuple(T...) +{ + T expand; + alias expand this; +} + +auto tuple(T...)(T values) +{ + return Tuple!T(values); +} + +enum union Value +{ + case Wrapped(Tuple!(int, string)); + case Record { Tuple!(int, int) point; int label; }; + case Nested(Tuple!(int, Tuple!(int, int))); +} + +int conditionEvaluations; + +Value nextValue() +{ + conditionEvaluations++; + return Value.Wrapped(tuple(8, "answer")); +} + +int inspectNext() +{ + return switch (nextValue()) + { + case Wrapped((number, text)) => number, + default => 0, + }; +} + +int inspect(Value value) +{ + return switch (value) + { + case Wrapped((number, text)) if (text == "answer") => number, + case Record { point: (x, y), label } => x + y + label, + case Nested((head, (left, right))) => head + left + right, + default => 0, + }; +} + +void main() +{ + assert(inspect(Value.Wrapped(tuple(42, "answer"))) == 42); + assert(inspect(Value.Record(tuple(1, 2), 3)) == 6); + assert(inspect(Value.Nested(tuple(1, tuple(2, 3)))) == 6); + assert(inspectNext() == 8); + assert(conditionEvaluations == 1); + + const Value qualified = Value.Wrapped(tuple(7, "answer")); + auto result = switch (qualified) + { + case Wrapped((number, text)) => + is(typeof(number) == const int) && is(typeof(text) == const string) + ? number : 0, + default => 0, + }; + assert(result == 7); +} diff --git a/compiler/test/runnable/enum_union_variant_spec.d b/compiler/test/runnable/enum_union_variant_spec.d new file mode 100644 index 000000000000..0d4b309dde3f --- /dev/null +++ b/compiler/test/runnable/enum_union_variant_spec.d @@ -0,0 +1,136 @@ +module enum_union_variant_spec; + +template AliasSeq(T...) { alias AliasSeq = T; } + +struct VariantMarker {} + +enum union Source +{ + case None(), + case Point(double x, double y), + case Move(double, double), + case Arc(double, double, double radius), + case User { int id; string name; }, + case Slice = const(ubyte)[], + @VariantMarker case int, +} + +void testReflection() +{ + alias Variants = __traits(allVariants, Source); + + static assert(__traits(variantKind, Variants[0]) == "unit"); + static assert(__traits(variantKind, Variants[1]) == "tuple"); + static assert(__traits(variantKind, Variants[2]) == "tuple"); + static assert(__traits(variantKind, Variants[3]) == "tuple"); + static assert(__traits(variantKind, Variants[4]) == "struct"); + static assert(__traits(variantKind, Variants[5]) == "alias"); + static assert(__traits(variantKind, Variants[6]) == "bare"); + static assert(!__traits(compiles, __traits(variantKind, double))); + static assert(__traits(getAttributes, Variants[6]).length == 1); + static assert(is(__traits(getAttributes, Variants[6])[0] == VariantMarker)); + + static foreach (index, Variant; Variants) + { + static assert(__traits(variantTag, Variant) == index); + static assert(__traits(isSame, Variant, Variants[index])); + static assert(__traits(isSame, __traits(parent, Variant), Source)); + static if (index == 6) + static assert(is(Variant == int)); + } + + static foreach (Variant; Variants) + static assert(__traits(variantParams, Variant).length == + __traits(variantParamNames, Variant).length); + + static assert(is(__traits(variantParams, Variants[1]) == AliasSeq!(double, double))); + static assert(__traits(variantParamNames, Variants[1]) == AliasSeq!("x", "y")); + static assert(is(__traits(variantParams, Variants[2]) == AliasSeq!(double, double))); + static assert(__traits(variantParamNames, Variants[2]) == AliasSeq!("", "")); + static assert(is(__traits(variantParams, Variants[3]) == AliasSeq!(double, double, double))); + static assert(__traits(variantParamNames, Variants[3]) == AliasSeq!("", "", "radius")); + static assert(is(__traits(variantParams, Variants[4]) == AliasSeq!(int, string))); + static assert(__traits(variantParamNames, Variants[4]) == AliasSeq!("id", "name")); + + static assert(__traits(hasVariant, Source, "Point")); + static assert(__traits(hasVariant, Source, "User")); + static assert(__traits(hasVariant, Source, int)); + static assert(!__traits(hasVariant, Source, "NonExistent")); + static assert(!__traits(hasVariant, Source, double)); + static assert(is(__traits(getVariant, Source, int) == int)); + + alias AliasDescriptor = Variants[5]; + alias BareDescriptor = Variants[6]; + static assert(__traits(isSame, AliasDescriptor, Variants[5])); + static assert(__traits(isSame, BareDescriptor, Variants[6])); +} + +enum union Copied +{ + static foreach (Variant; __traits(allVariants, Source)) + case __traits(variantDeclarationOf, Variant); +} + +enum union Deduplicated +{ + case None(); + static foreach (Variant; __traits(allVariants, Source)) + case __traits(variantDeclarationOf, Variant); +} + +enum union SamePayload +{ + case Alias = int; + case int; +} + +enum union SamePayloadCopied +{ + static foreach (Variant; __traits(allVariants, SamePayload)) + case __traits(variantDeclarationOf, Variant); +} + +enum union Target +{ + case __traits(variantDeclarationOf, __traits(getVariant, Source, "None")); + case __traits(variantDeclarationOf, __traits(getVariant, Source, "Point"), ""); + case __traits(variantDeclarationOf, __traits(getVariant, Source, int)); + case __traits(variantDeclarationOf, __traits(getVariant, Source, "Slice")); + case __traits(variantDeclarationOf, __traits(getVariant, Source, "Move"), "RelativeMove"); + case __traits(variantDeclarationOf, __traits(getVariant, Source, "User"), "Account"); + case __traits(variantDeclarationOf, __traits(getVariant, Source, int), "Code"); +} + +void testSplicing() +{ + Target t1 = Target.None(); + Target t2 = Target.Point(1.0, 2.0); + Target t3 = 404; + Target t4 = Target.Slice([ubyte(1), 2, 3]); + Target t5 = Target.RelativeMove(5.0, 10.0); + Target t6 = Target.Account(1, "Alice"); + Target t7 = Target.Code(500); + + static assert(is(Target.Account == struct)); + static assert(__traits(hasVariant, Target, int)); + static assert(__traits(hasVariant, Target, "Code")); + static assert(__traits(allVariants, Copied).length == 7); + static assert(__traits(allVariants, Deduplicated).length == 7); + alias SamePayloadVariants = __traits(allVariants, SamePayload); + static assert(__traits(variantTag, SamePayloadVariants[0]) == 0); + static assert(__traits(variantTag, SamePayloadVariants[1]) == 1); + alias CopiedSamePayloadVariants = __traits(allVariants, SamePayloadCopied); + static assert(CopiedSamePayloadVariants.length == 2); + static assert(__traits(variantKind, CopiedSamePayloadVariants[0]) == "alias"); + static assert(__traits(variantKind, CopiedSamePayloadVariants[1]) == "bare"); + assert(t1.__tag != t2.__tag); + assert(t3.__tag != t4.__tag); + assert(t5.__tag != t6.__tag); + assert(t7.__tag == __traits(variantTag, __traits(getVariant, Target, "Code"))); +} + +void main() +{ + testReflection(); + testSplicing(); +} diff --git a/compiler/test/runnable/testenumunion.d b/compiler/test/runnable/testenumunion.d new file mode 100644 index 000000000000..fbbbd24e1b51 --- /dev/null +++ b/compiler/test/runnable/testenumunion.d @@ -0,0 +1,830 @@ +import std.complex; + +alias None = typeof(null); +enum union Option(T) +{ + case Some(T), None; +} + +enum union Shape +{ + case Circle(double); + case Rectangle(double, double); + case Point(); +} + +void main() +{ + auto s1 = Shape.Circle(3.5); + auto s2 = Shape.Point; + assert(s1.__tag == 0); + assert(s2.__tag == 2); + + int getScore(Shape s) + { + return switch (s) + { + case Circle(r) => cast(int)(r * 2), + case Rectangle(w, h) => cast(int)(w * h), + case Point() => 1, + }; + } + assert(getScore(s1) == 7); + assert(getScore(s2) == 1); + assert(getScore(Shape.Rectangle(4.0, 5.0)) == 20); + + auto r1 = Shape.Rectangle(4.0, 5.0); + assert(r1.__tag == 1); + + Option!string opt = Option!string.Some("hello"); + assert(opt.__tag == 0); + Option!string empty = null; + assert(empty.__tag == 1); + + string text = switch (opt) + { + case Some(msg) => msg ~ " world", + case None => "empty", + }; + assert(text == "hello world"); + + testImplicitConversion(); + testBareTypes(); + testNullLikeBareType(); + testStructVariant(); + testHybridResponse(); + testGuards(); + testCompoundTypes(); + testCallables(); + testBareCompoundTypes(); + testBareCallables(); + testMemberFunctions(); + testExhaustiveness(); + testLifecycleCopyableVariant(); + testNamedArgumentsAndPatterns(); + testPatternMatrixExhaustiveness(); +} + +enum union NamedPatterns +{ + case Point(int x, int y); + case Square { int height; int width; }; +} + +void testNamedArgumentsAndPatterns() +{ + auto square = NamedPatterns.Square(height: 10, width: 5); + auto point = NamedPatterns.Point(4, y: 0); + + auto squareValue = switch (square) + { + case Square { height: h, width } if (h == 10) => width, + case Square { height: h, width: w } if (h == 10 && w == 10) => h, + case Square(height, width) => height + width, + case Point(...) => 0, + }; + assert(squareValue == 5); + + auto pointValue = switch (point) + { + case Point(x, y) if (x == 0 && y == 0) => 1, + case Point(x, y) if (x == 5 && y == 5) => 2, + case Point(x, y) => x + y, + case Square { ... } => 0, + }; + assert(pointValue == 4); + + auto rest = switch (square) + { + case Square { height, fields... } => fields[0], + case Point(...) => 0, + }; + assert(rest == 5); + + auto tupleRest = switch (square) + { + case Square(fields...) => fields[0] + fields[1], + case Point(...) => 0, + }; + assert(tupleRest == 15); +} + +enum union MatrixShape +{ + case Square { bool active; bool filled; }; +} + +void testPatternMatrixExhaustiveness() +{ + auto shape = MatrixShape.Square(true, false); + auto value = switch (shape) + { + case Square { active, filled } if (active && filled) => 1, + case Square { active, filled } if (active && !filled) => 2, + case Square { active, filled } if (!active && filled) => 3, + case Square { active, filled } => 4, + }; + assert(value == 2); +} + +// Regression test: implicit conversion of variant-construction expressions +// on `return` and when passing arguments to a function parameter. +enum union Account +{ + case User(int, string); + case Admin(int, string); +} + +Account getAccount() +{ + return Account.User(42, "alice"); // implicit conversion on return +} + +int accessAccount(Account a) // implicit conversion on argument passing +{ + return switch (a) + { + case User(id, name) => id, + case Admin(id, name) => -id, + }; +} + +void testImplicitConversion() +{ + assert(accessAccount(Account.Admin(7, "root")) == -7); + + Account a = getAccount(); + assert(accessAccount(a) == 42); +} + +// Regression test: bare (non-string) type variants construct correctly. +// (`switch` type-pattern matching currently only supports `double`/`string` +// bare-type arms, so `int`/`bool` are verified via `.__tag` and direct +// construction instead.) +enum union Val +{ + case int; + case bool; + case double; +} + +Val makeInt() { return Val(5); } +Val makeBool() { return Val(true); } +Val makeDouble() { return Val(3.14); } + +int classify(Val v) +{ + return switch (v) + { + case double d => cast(int) d, + default => -1, + }; +} + +void testBareTypes() +{ + Val vi = Val(5); + Val vb = Val(true); + Val vd = Val(3.14); + assert(vi.__tag == 0); + assert(vb.__tag == 1); + assert(vd.__tag == 2); + + assert(makeInt().__tag == 0); + assert(makeBool().__tag == 1); + assert(makeDouble().__tag == 2); + + assert(classify(Val(3.14)) == 3); + assert(classify(makeDouble()) == 3); +} + +enum union NullOption(T) +{ + case Some(T); + case typeof(null); +} + +void testNullLikeBareType() +{ + NullOption!int empty = null; + assert(switch (empty) + { + case Some(v) => v, + case typeof(null) => -1, + } == -1); + + Option!int empty2 = null; + assert(switch (empty2) + { + case Some(v) => v, + case None => -1, + } == -1); +} + +// Regression test: struct/record variants convert implicitly on +// both argument passing and `return`. +enum union Response2 +{ + case double; + case Success { int code; string payload; }; +} + +Response2 makeDouble2() { return 3.14; } +Response2 makeSuccess2() { return Response2.Success(200, "OK"); } + +string describe2(Response2 r) +{ + return switch (r) + { + case double d => "double", + case Success { code, ... } => "success", + }; +} + +void testStructVariant() +{ + assert(describe2(3.14) == "double"); + assert(describe2(Response2.Success(1, "ok")) == "success"); + assert(describe2(makeDouble2()) == "double"); + assert(describe2(makeSuccess2()) == "success"); +} + +// Regression test: `string` bare-type variants used to be misidentified by +// `switch` pattern matching (matched the wrong arm at runtime) because the +// unresolved `TypeIdentifier` for `string` was compared without running +// `typeSemantic()` on it first. See dcast.d/expressionsem.d fixes. +enum union Response +{ + case double; + case string; + case Success { int code; string payload; }; +} + +Response makeDouble3() { return 3.14; } +Response makeString3() { return "hello"; } +Response makeSuccess3() { return Response.Success(200, "OK"); } + +string describe(Response r) +{ + return switch (r) + { + case double d => "double", + case string s => "string", + case Success { code, ... } => "success", + }; +} + +void testHybridResponse() +{ + assert(describe(3.14) == "double"); + assert(describe("hi") == "string"); + assert(describe(Response.Success(1, "ok")) == "success"); + assert(describe(makeDouble3()) == "double"); + assert(describe(makeString3()) == "string"); + assert(describe(makeSuccess3()) == "success"); +} + +// Regression test: `if` guards on switch expression arms, including +// multiple guarded arms for the same variant, a plain (unguarded) arm for +// the same variant as a guard-fallback, and bindings that are visible to +// both the guard condition and the arm action. +string classifyShape(Shape s) +{ + return switch (s) + { + case Circle(r) if (r > 10.0) => "big circle", + case Circle(r) if (r <= 10.0) => "small circle", + case Rectangle(w, h) if (w == h) => "square", + case Rectangle(w, h) => "rectangle", + case Point() => "point", + default => "unreachable", + }; +} + +enum union GuardVal +{ + case double; + case string; +} + +string classifyGuardVal(GuardVal v) +{ + return switch (v) + { + case double d if (d > 0.0) => "positive double", + case double d => "non-positive double", + case string s => "string", + }; +} + +void testGuards() +{ + assert(classifyShape(Shape.Circle(20.0)) == "big circle"); + assert(classifyShape(Shape.Circle(3.0)) == "small circle"); + assert(classifyShape(Shape.Rectangle(4.0, 4.0)) == "square"); + assert(classifyShape(Shape.Rectangle(4.0, 5.0)) == "rectangle"); + assert(classifyShape(Shape.Point) == "point"); + + assert(classifyGuardVal(5.0) == "positive double"); + assert(classifyGuardVal(-5.0) == "non-positive double"); + assert(classifyGuardVal("hi") == "string"); +} + +// Regression test: enum union variants wrapping "compound" payload types: +// arrays, associative arrays, pointers, void[]/void*, noreturn*/noreturn[], +// function/delegate pointers, char/wchar/dchar, string/wstring/dstring, +// static arrays, and complex/imaginary numerics. +enum union CompoundTypes +{ + case Arr(int[]); + case AssocArr(int[string]); + case Ptr(int*); + case VoidArr(void[]); + case VoidPtr(void*); + case NoReturnPtr(noreturn*); + case NoReturnArr(noreturn[]); + case FuncPtr(int function(int)); + case Del(int delegate(int)); + case Ch(char); + case WCh(wchar); + case DCh(dchar); + case Str(string); + case WStr(wstring); + case DStr(dstring); + case StaticArr(int[4]); + case Cplx(Complex!double); + case Imag(double); +} + +private int addOne(int x) { return x + 1; } + +void testCompoundTypes() +{ + int local = 42; + int[string] aa; + aa["x"] = 1; + + auto arr = CompoundTypes.Arr([1, 2, 3]); + auto assocArr = CompoundTypes.AssocArr(aa); + auto ptr = CompoundTypes.Ptr(&local); + auto voidArr = CompoundTypes.VoidArr(cast(void[])[1, 2, 3]); + auto voidPtr = CompoundTypes.VoidPtr(cast(void*)&local); + auto noReturnPtr = CompoundTypes.NoReturnPtr(null); + auto noReturnArr = CompoundTypes.NoReturnArr([]); + auto funcPtr = CompoundTypes.FuncPtr(&addOne); + int delegate(int) dg = (int x) => x * 2; + auto del = CompoundTypes.Del(dg); + auto ch = CompoundTypes.Ch('a'); + auto wch = CompoundTypes.WCh('b'); + auto dch = CompoundTypes.DCh('c'); + auto str = CompoundTypes.Str("hello"); + auto wstr = CompoundTypes.WStr("world"w); + auto dstr = CompoundTypes.DStr("!"d); + int[4] sa = [1, 2, 3, 4]; + auto staticArr = CompoundTypes.StaticArr(sa); + auto cplx = CompoundTypes.Cplx(Complex!double(1.0, 2.0)); + auto imag = CompoundTypes.Imag(3.0); + + assert(arr.__tag == 0); + assert(assocArr.__tag == 1); + assert(ptr.__tag == 2); + assert(voidArr.__tag == 3); + assert(voidPtr.__tag == 4); + assert(noReturnPtr.__tag == 5); + assert(noReturnArr.__tag == 6); + assert(funcPtr.__tag == 7); + assert(del.__tag == 8); + assert(ch.__tag == 9); + assert(wch.__tag == 10); + assert(dch.__tag == 11); + assert(str.__tag == 12); + assert(wstr.__tag == 13); + assert(dstr.__tag == 14); + assert(staticArr.__tag == 15); + assert(cplx.__tag == 16); + assert(imag.__tag == 17); + + int sum = switch (arr) + { + case Arr(a) => a[0] + a[1] + a[2], + default => -1, + }; + assert(sum == 6); + + int v = switch (assocArr) + { + case AssocArr(a) => a["x"], + default => -1, + }; + assert(v == 1); + + int derefed = switch (ptr) + { + case Ptr(p) => *p, + default => -1, + }; + assert(derefed == 42); + + size_t voidArrLen = switch (voidArr) + { + case VoidArr(a) => a.length, + default => size_t.max, + }; + assert(voidArrLen == 12); // 3 ints * 4 bytes + + bool voidPtrNonNull = switch (voidPtr) + { + case VoidPtr(p) => p !is null, + default => false, + }; + assert(voidPtrNonNull); + + int called = switch (funcPtr) + { + case FuncPtr(f) => f(9), + default => -1, + }; + assert(called == 10); + + int doubled = switch (del) + { + case Del(d) => d(9), + default => -1, + }; + assert(doubled == 18); + + char c = switch (ch) + { + case Ch(x) => x, + default => '?', + }; + assert(c == 'a'); + + string s = switch (str) + { + case Str(x) => x, + default => "", + }; + assert(s == "hello"); + + int staticArrSum = switch (staticArr) + { + case StaticArr(a) => a[0] + a[1] + a[2] + a[3], + default => -1, + }; + assert(staticArrSum == 10); +} + +// Regression test: enum union variants holding a plain function and +// multiple delegates. Reassigning between variants (and within the same +// variant) with different closures must never leave a stale/mixed-up +// delegate context pointer behind. +enum union Callable +{ + case Fn(int function(int)); + case Dg(int delegate(int)); + case Dg2(int delegate(int)); +} + +private int call(Callable c) +{ + return switch (c) + { + case Fn(f) => f(1), + case Dg(d) => d(1), + case Dg2(d) => d(1), + }; +} + +private int makeClosureAndCall(int captured) +{ + int delegate(int) dg = (int x) => x + captured; + return call(Callable.Dg(dg)); +} + +enum union Handler +{ + case OnClick { int delegate(int) callback; }; + case OnHover { int delegate(int) callback; }; +} + +private int callHandler(Handler h, int x) +{ + return switch (h) + { + case OnClick { callback } => callback(x), + case OnHover { callback } => callback(x), + }; +} + +void testCallables() +{ + Callable cf = Callable.Fn((int x) => x + 1); + assert(call(cf) == 2); + + // Two delegates capturing DIFFERENT locals: a stale/mixed-up context + // pointer would silently add the wrong value. + int a = 100; + int b = 200; + int delegate(int) dgA = (int x) => x + a; + int delegate(int) dgB = (int x) => x + b; + + Callable c1 = Callable.Dg(dgA); + assert(call(c1) == 101); + + // Reassign the SAME variant tag (Dg -> Dg) with a different closure. + c1 = Callable.Dg(dgB); + assert(call(c1) == 201); + + // Reassign to a DIFFERENT variant tag (Dg -> Dg2) with yet another closure. + int cCap = 300; + int delegate(int) dgC = (int x) => x + cCap; + c1 = Callable.Dg2(dgC); + assert(call(c1) == 301); + + // Reassign back to Dg with dgA: context must be dgA's, not dgC's leftover. + c1 = Callable.Dg(dgA); + assert(call(c1) == 101); + + // Each call creates a distinct closure; verifies no cross-contamination. + assert(makeClosureAndCall(5) == 6); + assert(makeClosureAndCall(50) == 51); + + // Common delegate field, accessed via switch. + int clicks, hovers; + int delegate(int) onClick = (int x) { clicks += x; return clicks; }; + int delegate(int) onHover = (int x) { hovers += x; return hovers; }; + + Handler h = Handler.OnClick(onClick); + assert(callHandler(h, 5) == 5); + assert(clicks == 5 && hovers == 0); + + h = Handler.OnHover(onHover); + assert(callHandler(h, 3) == 3); + assert(hovers == 3 && clicks == 5); + + // Reassign the same variant with a different closure; the switch-based + // call must use the new context, not the previous one. + int otherClicks; + int delegate(int) onClick2 = (int x) { otherClicks += x * 2; return otherClicks; }; + h = Handler.OnClick(onClick2); + assert(callHandler(h, 4) == 8); + assert(otherClicks == 8 && clicks == 5); +} + +// Regression test: all the compound types tested as NAMED variant payloads +// above also work as bare (unnamed) variant types, including identifier- +// spelled types followed by `*`/`[]` (e.g. `noreturn*`), which previously +// mis-parsed as a named unit variant called e.g. `noreturn`. +enum union BareCompoundTypes +{ + case int[]; + case int[string]; + case int*; + case void[]; + case void*; + case noreturn*; + case noreturn[]; + case int function(int); + case int delegate(int); + case char; + case wchar; + case dchar; + case string; + case wstring; + case dstring; + case int[4]; + case float; + case real; + case double; +} + +void testBareCompoundTypes() +{ + int local = 7; + int[string] aa; + aa["y"] = 2; + int[4] sa = [1, 2, 3, 4]; + + BareCompoundTypes v0 = BareCompoundTypes([1, 2, 3]); + BareCompoundTypes v1 = BareCompoundTypes(aa); + BareCompoundTypes v2 = BareCompoundTypes(&local); + BareCompoundTypes v3 = BareCompoundTypes(cast(void[])[1, 2, 3]); + BareCompoundTypes v4 = BareCompoundTypes(cast(void*)&local); + BareCompoundTypes v5 = BareCompoundTypes(cast(noreturn*) null); + BareCompoundTypes v6 = BareCompoundTypes(cast(noreturn[])[]); + int function(int) plainFn = (int x) => x + 1; + BareCompoundTypes v7 = BareCompoundTypes(plainFn); + BareCompoundTypes v8 = BareCompoundTypes((int x) => x + local); + BareCompoundTypes v9 = BareCompoundTypes('a'); + BareCompoundTypes v10 = BareCompoundTypes(cast(wchar)'b'); + BareCompoundTypes v11 = BareCompoundTypes(cast(dchar)'c'); + BareCompoundTypes v12 = BareCompoundTypes("hello"); + BareCompoundTypes v13 = BareCompoundTypes("world"w); + BareCompoundTypes v14 = BareCompoundTypes("!"d); + BareCompoundTypes v15 = BareCompoundTypes(sa); + BareCompoundTypes v16 = BareCompoundTypes(1.0f); + BareCompoundTypes v17 = BareCompoundTypes(3.0L); + + assert(v0.__tag == 0); + assert(v1.__tag == 1); + assert(v2.__tag == 2); + assert(v3.__tag == 3); + assert(v4.__tag == 4); + assert(v5.__tag == 5); + assert(v6.__tag == 6); + assert(v7.__tag == 7); + assert(v8.__tag == 8); + assert(v9.__tag == 9); + assert(v10.__tag == 10); + assert(v11.__tag == 11); + assert(v12.__tag == 12); + assert(v13.__tag == 13); + assert(v14.__tag == 14); + assert(v15.__tag == 15); + assert(v16.__tag == 16); + assert(v17.__tag == 17); +} + + +// Regression test: bare function-pointer and delegate variants with the same +// signature. A lambda's inferred attributes (`pure nothrow @nogc @safe`) +// used to prevent implicit construction entirely, since the enum union +// implicit-conversion check required an exact type match; attribute +// widening is now allowed specifically for callable payload types (it can +// never introduce cross-variant ambiguity the way numeric widening would). +enum union Funs +{ + case int function(int); + case int delegate(int); +} + +void testBareCallables() +{ + // A non-capturing lambda is convertible to *either* a function pointer or + // a delegate with the same signature, so assigning it directly to `Funs` + // is genuinely ambiguous and must go through an explicitly-typed + // intermediate (same as f3/f4 below); a capturing closure, however, can + // only ever be a delegate, so it unambiguously selects that variant. + int function(int) plainFn = (int x) => x + 1; + Funs f1 = plainFn; // decays to a plain function pointer + int captured = 10; + Funs f2 = (int x) => x + captured; // closure -> delegate (unambiguous) + + assert(f1.__tag == 0); + assert(f2.__tag == 1); + + int function(int) fp = (int x) => x + 1; + int delegate(int) dg = (int x) => x + captured; + Funs f3 = fp; + Funs f4 = dg; + assert(f3.__tag == 0); + assert(f4.__tag == 1); +} + +// Regression test: `enum union` member declarations (functions, static +// functions, manifest constants) after a `;` following the variant list, per +// the grammar's `(";" MemberDeclarationList)?`. Member functions can use +// `switch (this)` to dispatch on the active variant, and named-variant +// factory functions (`Shape.Circle(...)`) still get synthesized correctly +// alongside user-declared members. +enum union ShapeWithMethods +{ + case Circle(double); + case Rectangle(double, double); + case Point(); + + double area() + { + return switch (this) + { + case Circle(r) => 3.14159 * r * r, + case Rectangle(w, h) => w * h, + case Point() => 0.0, + }; + } + + string describe() + { + return switch (this) + { + case Circle(r) => "circle", + case Rectangle(w, h) => "rectangle", + case Point() => "point", + }; + } + + static ShapeWithMethods unit() { return ShapeWithMethods.Point; } + + enum string kind = "shape"; +} + +void testMemberFunctions() +{ + ShapeWithMethods c = ShapeWithMethods.Circle(2.0); + assert(c.describe() == "circle"); + assert(c.area() > 12.5 && c.area() < 12.6); + + ShapeWithMethods r = ShapeWithMethods.Rectangle(3.0, 4.0); + assert(r.describe() == "rectangle"); + assert(r.area() == 12.0); + + ShapeWithMethods u = ShapeWithMethods.unit(); + assert(u.describe() == "point"); + assert(u.area() == 0.0); + + assert(ShapeWithMethods.kind == "shape"); +} + +// Regression test: exhaustiveness checking. A `switch` covering every +// variant (unguarded) needs no `default` and compiles/runs fine; combining +// a guarded arm, an unguarded fallback for the same variant, and a +// `default` for the rest is exhaustive and non-redundant. +enum union Traffic +{ + case Red(); + case Yellow(); + case Green(); +} + +string classifyTraffic(Traffic t) +{ + return switch (t) + { + case Red() => "stop", + case Yellow() => "caution", + case Green() => "go", + }; +} + +enum union Level +{ + case double; + case string; +} + +string classifyLevel(Level v) +{ + return switch (v) + { + case double d if (d > 100.0) => "high", + case double d => "normal", + default => "other", + }; +} + +void testExhaustiveness() +{ + assert(classifyTraffic(Traffic.Red) == "stop"); + assert(classifyTraffic(Traffic.Yellow) == "caution"); + assert(classifyTraffic(Traffic.Green) == "go"); + + assert(classifyLevel(150.0) == "high"); + assert(classifyLevel(50.0) == "normal"); + assert(classifyLevel("hi") == "other"); +} + +int lifecycleCopyCount; + +struct CopyablePayload +{ + int x; + this(ref CopyablePayload other) + { + lifecycleCopyCount++; + x = other.x; + } +} + +enum union WithCopyable +{ + case Wrapped(CopyablePayload); + case Flag(bool); +} + +void testLifecycleCopyableVariant() +{ + lifecycleCopyCount = 0; + WithCopyable a = WithCopyable.Wrapped(CopyablePayload(7)); + WithCopyable b = a; + assert(lifecycleCopyCount == 2); + assert(a.__tag == 0); + assert(b.__tag == 0); +} + +import std.traits: isBuiltinType; + +enum union TemplateVarargs(Types...) +{ + static foreach(U; Types) + static if (isBuiltinType!U) + case U; +} + +void testTemplateVarargs() +{ + TemplateVarargs!(int, void*, string) t = "asdf"; + assert(t.__tag == 2); + t = 42; + assert(t.__tag == 0); +} diff --git a/compiler/test/unit/lexer/location_offset.d b/compiler/test/unit/lexer/location_offset.d index d599946b387a..91009ee33929 100644 --- a/compiler/test/unit/lexer/location_offset.d +++ b/compiler/test/unit/lexer/location_offset.d @@ -461,6 +461,7 @@ enum Test[string] tests = [ enum ignoreTokens { reserved, + enumUnion, negate, array, call, diff --git a/compiler/test/unit/parser/test_astbase.d b/compiler/test/unit/parser/test_astbase.d index a842643d77f6..67d3c8eb38ca 100644 --- a/compiler/test/unit/parser/test_astbase.d +++ b/compiler/test/unit/parser/test_astbase.d @@ -1,5 +1,49 @@ module parser.test_astbase; +import dmd.astbase; +import dmd.visitor.transitive; +import support : afterEach, beforeEach; + +@beforeEach +void initializeFrontend() +{ + import dmd.frontend : initDMD; + initDMD(); +} + +@afterEach +void deinitializeFrontend() +{ + import dmd.frontend : deinitializeDMD; + deinitializeDMD(); +} + +extern (C++) class CountingVisitor : ParseTimeTransitiveVisitor!ASTBase +{ + alias visit = ParseTimeTransitiveVisitor!ASTBase.visit; + size_t variables; + size_t payloadTypes; + size_t attributes; + + override void visit(ASTBase.VarDeclaration declaration) + { + ++variables; + ParseTimeTransitiveVisitor!ASTBase.visit(declaration); + } + + override void visit(ASTBase.TypeIdentifier type) + { + ++payloadTypes; + ParseTimeTransitiveVisitor!ASTBase.visit(type); + } + + override void visit(ASTBase.IdentifierExp expression) + { + ++attributes; + ParseTimeTransitiveVisitor!ASTBase.visit(expression); + } +} + // Simple test to check whether ASTBase respects the interface // that the parser expects from an AST family @@ -13,3 +57,30 @@ void main() scope parser = new Parser!ASTBase(null, null, false, new ErrorSinkStderr, null, false); assert(parser !is null); } + +@("enum union transitive visitor") +unittest +{ + import dmd.errorsink; + import dmd.parse; + + enum source = q{ + enum union Result + { + @Marker case Some(Payload); + case None(); + } + }; + + scope parser = new Parser!ASTBase(null, source, false, new ErrorSinkStderr, null, false); + parser.nextToken(); + auto declarations = parser.parseModule(); + + scope visitor = new CountingVisitor(); + foreach (declaration; *declarations) + declaration.accept(visitor); + + assert(visitor.variables == 0); + assert(visitor.payloadTypes == 1); + assert(visitor.attributes == 1); +}