Fast path for __traits(getMember, ...) in is() type expressions#22542
Closed
CyberShadow wants to merge 1 commit intodlang:masterfrom
Closed
Fast path for __traits(getMember, ...) in is() type expressions#22542CyberShadow wants to merge 1 commit intodlang:masterfrom
CyberShadow wants to merge 1 commit intodlang:masterfrom
Conversation
Add lightweight resolution for `is(__traits(getMember, T, "name") ...)` that uses `sym.search()` to resolve the member type without triggering expressionSemantic/functionSemantic. This prevents circular dependency errors when iterating allMembers on types with auto-return methods, and also fixes `is(__traits(getMember, T, "func") R == return)` which previously always returned false because getMember resolved to a Dsymbol rather than a Type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
|
Thanks for your pull request, @CyberShadow! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + dmd#22542" |
Member
Author
|
(I assume the same objection as in #22541 (review) applies.) |
This was referenced Feb 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Generated by Claude Opus 4.6, usual disclaimers apply.
Problem
D's compile-time introspection allows probing member return types via
is(__traits(getMember, T, "name") R == return). This is useful in ORM frameworks, serialization libraries, and other metaprogramming patterns where a type's methods iterateallMembersto discover which members return a specific type:This fails with two separate issues:
Circular dependency:
is(__traits(getMember, Table, "save") ...)triggersexpressionSemantic→functionSemantic→functionSemantic3(body compilation) onsave()to infer its return type. Butsave()'s body referenceshasIntReturning(), which is already being compiled — circular dependency.Silently broken: Even without the circular dependency,
is(__traits(getMember, S, "func") R == return)always evaluates to false on current master.getMemberresolves to aDsymbol(FuncDeclaration), not aType, so theIsExphandler'strySemanticcannot convert it to aTypeFunction— the return type query silently fails.Analysis
The chain that produces the circular dependency error is:
static assertevaluateshasIntReturning()via CTFE, which starts compiling its body (semantic3)allMembers, which includes"save". For that member,__traits(getMember, Table, "save")inside theis()expression callsTypeTraits.resolve→semanticTraits(getMember)→expressionSemanticfunctionSemanticonsave()save()has anautoreturn type, sofunctionSemanticeagerly callsfunctionSemantic3(body compilation) to infer the return typefunctionSemantic3ungags errors even insidetrySemantic's gagged context, so the circular dependency error propagatessave()'s body containsenum h = hasIntReturning(), which tries to CTFEhasIntReturning()— circular dependencyThe separate "always false" issue occurs because
TypeTraits.resolvefor a function member returns aDsymbol(theFuncDeclaration), not aType. TheIsExphandler expects aTypefromtrySemantic, getsnull, and evaluates the condition as false.Observation
is(__traits(getMember, T, "name") R == return)only needs the member's type — specifically, itsTypeFunction. For function declarations,fd.typeis populated afterdsymbolSemantic(pass 1), which resolves explicit return types and parameter types without compiling the function body. Forautoreturn methods where.next(return type) is stillnull, theis(... == return)condition can simply evaluate to false — the return type genuinely isn't known yet, and that's the correct answer without needing to force body compilation.Similarly, for variable members,
vd.typeis available after dsymbolSemantic and is sufficient for type comparisons.Proposal
Add a fast path in the
IsExphandler (expressionsem.d) that detects whene.targis aTypeTraitswrapping__traits(getMember, T, "name")and resolves the member's type via lightweightsym.search()instead of fullsemanticTraits→expressionSemantic. This:is(__traits(getMember, T, "func") R == return)by returning theTypeFunctiondirectlyfunctionSemantic/functionSemantic3trySemanticpath for any case the fast path can't handleThe fast path is scoped to
IsExponly — it does not affectaliasdeclarations or other type contexts whereTypeTraits.resolveneeds to return the fullDsymbol.Implementation
A
packagehelpergetMemberTypeFastPathis added tocompiler/src/dmd/typesem.d. It is called from theIsExphandler incompiler/src/dmd/expressionsem.d, before the existingtrySemanticcall:e.targis aTypeTraitswhose inner expression is__traits(getMember, ...)with exactly 2 argumentsTemplateInstance_semanticTiargson the getMember arguments — resolves type and string without body compilationIdentifiergetDsymbol()on the first argumentsym.search(loc, id)— pure symbol table lookup, no semantic analysisfd.typeforFuncDeclaration,vd.typeforVarDeclarationThe helper returns
nullat any step that fails, falling back to the existing full-semantic path. This ensures no behavioral regressions — if the fast path can't handle a case, the old code takes over transparently.Trade-offs
autoreturn types resolve to false, not inferred: For methods withautoreturn types where the return type hasn't been inferred yet (.nextisnull),is(__traits(getMember, T, "func") R == return)evaluates to false rather than triggering body compilation to infer the type. This is the correct conservative answer — the return type genuinely isn't available without body compilation, and forcing it is what causes the circular dependency. Code that needs the inferred return type can use the two-step form:Scoped to
IsExponly: The fast path is deliberately not placed inTypeTraits.resolvebecause that would change behavior foraliasdeclarations —alias member = __traits(getMember, S, "x")would alias the member's type instead of the member symbol. By limiting the fast path toIsExp, onlyis(...)expressions are affected, which is the context where aType(notDsymbol) is the correct result.Test
compiler/test/compilable/test_getMember_type_fwdref.dcovers:is(__traits(getMember, S, "func") R == return)resolves explicit return types (broken on master — always false)is(__traits(getMember, S, "x") == int)resolves field typesallMembers+is(getMember == return)loop withauto save()method — circular dependency on mastertypeof(__traits(getMember, ...))still works as before