Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
63 changes: 63 additions & 0 deletions compiler/src/dmd/traits.d
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,60 @@ private Dsymbol getDsymbolWithoutExpCtx(RootObject oarg)
return getDsymbol(oarg);
}

/**
* Fast path for `__traits(getAttributes, __traits(getMember, T, "name"))`.
*
* Resolves the member via `sym.search()` without triggering `functionSemantic`,
* avoiding the eager body compilation that can cause forward reference errors
* in introspection-heavy code. Falls back to `null` if the pattern doesn't
* match or the lightweight resolution fails.
*/
private Expression getAttributesOfMemberFastPath(TraitsExp e, Scope* sc)
{
auto inner = isExpression((*e.args)[0]);
if (!inner)
return null;
auto te = inner.isTraitsExp();
if (!te || te.ident != Id.getMember)
return null;
if (!te.args || te.args.length != 2)
return null;

// Resolve getMember's own arguments (T and "name") — this is cheap,
// just type resolution and string evaluation, no body compilation.
if (!TemplateInstance_semanticTiargs(te.loc, sc, te.args, 3))
return null;

// Extract the member name string.
auto ex = isExpression((*te.args)[1]);
if (!ex)
return null;
ex = ex.ctfeInterpret();
auto se = ex.toStringExp();
if (!se || se.len == 0)
return null;
se = se.toUTF8(sc);
if (se.sz != 1)
return null;
auto id = Identifier.idPool(se.peekString());

// Get the Dsymbol for the type/aggregate.
Dsymbol sym = getDsymbol((*te.args)[0]);
if (!sym)
return null;

// Lightweight member lookup (same mechanism hasMember uses).
auto sm = sym.search(e.loc, id);
if (!sm)
return null;

// Read UDAs directly from the Dsymbol — no expressionSemantic needed.
auto udad = sm.userAttribDecl;
auto exps = udad ? udad.getAttributes() : new Expressions();
auto tup = new TupleExp(e.loc, exps);
return tup.expressionSemantic(sc);
}

/**
* Fill an array of target size_t values that indicate possible pointer words in memory
* if interpreted as the type given as argument.
Expand Down Expand Up @@ -1298,6 +1352,15 @@ Expression semanticTraits(TraitsExp e, Scope* sc)
}
if (e.ident == Id.getAttributes)
{
// Fast path: __traits(getAttributes, __traits(getMember, T, "name"))
// Resolves the member via sym.search() without triggering functionSemantic,
// avoiding eager body compilation that can cause forward reference errors.
if (dim == 1)
{
if (auto result = getAttributesOfMemberFastPath(e, sc))
return result;
}

/* 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
Expand Down
74 changes: 74 additions & 0 deletions compiler/test/compilable/test_getattr_getMember_fwdref.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* __traits(getAttributes, __traits(getMember, ...)) should resolve member UDAs
* via lightweight symbol lookup without triggering full expressionSemantic on the
* member. This avoids eager body compilation (functionSemantic3) of method members,
* which can cause circular dependency errors when a method's body references the
* introspecting function at compile time.
*/

struct Column { string name; }

/******** Template struct case ********/

struct Table(T)
{
@Column("id") T id;
@Column("data") T data;

static string[] columnNames()
{
string[] result;
static foreach (name; __traits(allMembers, Table)) {{
static foreach (attr; __traits(getAttributes, __traits(getMember, Table, name))) {
static if (is(typeof(attr) == Column))
result ~= attr.name;
}
}}
return result;
}

// Auto-return method that references columnNames at compile time.
// Without the fast path, __traits(getMember, Table, "save") triggers
// functionSemantic3 (for return type inference) on save(), whose body
// tries to CTFE columnNames() while it's already being compiled,
// causing "circular dependency" error.
auto save()
{
enum cols = columnNames();
return cols.length;
}
}

static assert(Table!int.columnNames() == ["id", "data"]);

/******** Mixin template case ********/

mixin template Model()
{
static string[] modelColumnNames()
{
string[] result;
static foreach (name; __traits(allMembers, typeof(this))) {{
static foreach (attr; __traits(getAttributes, __traits(getMember, typeof(this), name))) {
static if (is(typeof(attr) == Column))
result ~= attr.name;
}
}}
return result;
}

auto persist()
{
enum cols = modelColumnNames();
return cols.length;
}
}

struct User
{
@Column("user_id") int id;
@Column("username") string name;
mixin Model;
}

static assert(User.modelColumnNames() == ["user_id", "username"]);
Loading