Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
408 changes: 408 additions & 0 deletions compiler/docs/enum_union_guide.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions compiler/src/dmd/astbase.d
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ struct ASTBase
alias Parameters = Array!(Parameter);
alias Statements = Array!(Statement);
alias Catches = Array!(Catch);
inout(SwitchExp) isSwitchExp() { return op == EXP.switchExpression ? cast(typeof(return))this : null; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't look like the right place for it.

alias Identifiers = Array!(Identifier);
alias Initializers = Array!(Initializer);
alias Ensures = Array!(Ensure);
Expand Down
91 changes: 91 additions & 0 deletions compiler/src/dmd/dcast.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -113,6 +114,76 @@ 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;
foreach (i, variant; eu.variants)
{
auto payloadType = variant.payloadType && variant.payloadType.fields.length
? variant.payloadType.fields[0].type : variant.payload.length ? variant.payload[0] : null;
// Function pointers/delegates may only differ from each other by
// attributes (e.g. inferred `pure nothrow @nogc @safe` on a lambda),
// never by parameter/return types, so allowing attribute-widening
// implicit conversion here cannot introduce cross-variant ambiguity
// the way e.g. `int` -> `double` numeric widening would. Only loosen
// when the source is ITSELF already callable: other types (e.g.
// `noreturn*`) can have unrelated implicit conversions to callable
// types that must not be treated the same way.
const requiredMatch = payloadType && payloadType.isFunction_Delegate_PtrToFunction() &&
e.type && e.type.isFunction_Delegate_PtrToFunction()
? MATCH.convert : MATCH.exact;
const isNullUnitVariant = e.type && e.type.toBasetype().ty == Tnull &&
variant.payload.length == 0 && variant.ident == Identifier.idPool("None");
if ((!variant.ident && variant.payload.length == 1 && payloadType &&
e.implicitConvTo(payloadType) >= requiredMatch) || isNullUnitVariant)
{
if (matchIndex != size_t.max)
{
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()
: "None",
payloadType ? payloadType.toErrMsg() : "None",
eu.toPrettyChars());
return ErrorExp.get();
}
matchIndex = i;
}
}
if (matchIndex != size_t.max)
{
auto variant = eu.variants[matchIndex];
// Build `(tmp; tmp.__tag = i; tmp.<payloadVar>.<field> = e; tmp)`
// rather than a positional struct literal: the payload union
// promotes one field per variant, so a 2-element literal would
// target the wrong (first) union member when there is more
// than one bare-type variant.
auto tmp = new VarDeclaration(e.loc, t, Identifier.generateId("__enumConv"), null);
tmp.storage_class |= STC.temp;
Expression result = new DeclarationExp(e.loc, tmp);
Expression tmpVar = new VarExp(e.loc, tmp);

auto tagExp = new DotVarExp(e.loc, tmpVar, eu.tagVar);
Expression tagAssign = new AssignExp(e.loc, tagExp, new IntegerExp(e.loc, matchIndex, Type.tuns8));
result = new CommaExp(e.loc, result, tagAssign);

if (variant.payloadType && variant.payloadType.fields.length)
{
auto field = variant.payloadType.fields[0];
auto payloadAccess = new DotVarExp(e.loc,
new DotVarExp(e.loc, tmpVar, variant.payloadVar), field);
Expression payloadAssign = new AssignExp(e.loc, payloadAccess, e);
result = new CommaExp(e.loc, result, payloadAssign);
}

result = new CommaExp(e.loc, result, tmpVar);
return result.expressionSemantic(sc);
}
}
}

Expression visit(Expression e)
{
//printf("Expression.implicitCastTo(%s of type %s) => %s\n", e.toChars(), e.type.toChars(), t.toChars());
Expand Down Expand Up @@ -1561,6 +1632,26 @@ 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 payloadType = 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;
const isNullUnitVariant = from.toBasetype().ty == Tnull &&
variant.payload.length == 0 && variant.ident == Identifier.idPool("None");

@rikkimax rikkimax Aug 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to avoid comparing identifiers by string, chuck None into the table and do a pointer comparison instead.

if ((!variant.ident && variant.payload.length == 1 && payloadType &&
from.implicitConvTo(payloadType) >= requiredMatch) || isNullUnitVariant)
return MATCH.convert;
}
}
}

MATCH visitType(Type from)
{
//printf("Type::implicitConvTo(this=%p, to=%p)\n", this, to);
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dmd/dfa/fast/expression.d
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,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;
Expand Down
45 changes: 45 additions & 0 deletions compiler/src/dmd/dstruct.d
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ 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.func;
Expand All @@ -33,6 +35,15 @@ enum StructFlags : int
hasPointers = 0x1, // NB: should use noPointers as in ClassFlags
}

struct EnumUnionVariant
{
Identifier ident;
Type[] payload;
Dsymbols* members;
StructDeclaration payloadType;
VarDeclaration payloadVar;
}

/***********************************************************
* All `struct` declarations are an instance of this.
*/
Expand Down Expand Up @@ -139,6 +150,40 @@ extern (C++) class StructDeclaration : AggregateDeclaration
}


/***********************************************************
* Tagged aggregate used by `enum union` declarations.
*/
extern (C++) final class EnumUnionDeclaration : StructDeclaration
{
EnumUnionVariant[] variants;
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 = variants;
StructDeclaration.syntaxCopy(eu);
return eu;
}

override const(char)* kind() const
{
return "enum union";
}

override void accept(Visitor v)
{
v.visit(this);
}
}

/***********************************************************
* Unions are a variation on structs.
*/
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dmd/dsymbol.d
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ enum DSYM : ubyte
classDeclaration,
structDeclaration,
unionDeclaration,
enumUnionDeclaration,
interfaceDeclaration,
scopeDsymbol,
forwardingScopeDsymbol,
Expand Down Expand Up @@ -976,6 +977,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;
Expand Down Expand Up @@ -1033,8 +1035,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)
Expand All @@ -1050,6 +1053,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:
Expand Down
Loading
Loading