feat: support DebuggerDisplayAttribute display templates#231
Closed
GustavEikaas wants to merge 3 commits into
Closed
feat: support DebuggerDisplayAttribute display templates#231GustavEikaas wants to merge 3 commits into
GustavEikaas wants to merge 3 commits into
Conversation
When a class or struct declares a ToString() override (true 'override',
not 'new' shadowing; base types walked, stopping at System.Object,
System.ValueType and System.Exception to keep exception display intact),
use its result instead of the default {TypeName} display.
The override is located via metadata during the base-type walk and called
directly on the ICorDebugValue the debugger already holds (receiver-bound
EvalFunction), not by re-evaluating the variable's evaluateName as text.
This avoids re-executing watch expressions with side effects and covers
children that have no usable evaluateName.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Promote ForEachAttribute to a public helper that exposes the raw attribute value blob (ECMA-335 II.23.3) to its callback, and move the inline DebuggerBrowsableAttribute blob check from evaluator.cpp into attributes.cpp as HasDebuggerBrowsableNeverAttribute. No behavior change; this gives custom attribute argument decoding a single home. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use DebuggerDisplayAttribute.Value as the variable display for classes
and structs: literal text with {expression} placeholders, where v1
supports instance fields, properties, zero-argument methods, dotted
member chains, 'this' and the ',nq' modifier. Escaped '{{'/'}}' braces
are kept literal. The attribute is looked up on the exact type with
base type fallback, and takes precedence over an overridden ToString().
Placeholders are evaluated against the displayed value itself
(receiver-bound ResolveIdentifiers/EvalFunction), not the current
frame, and their results are printed with plain PrintValue, so display
evaluation can not recurse. Any unsupported or failed placeholder
conservatively keeps the {TypeName} display and never breaks variable
retrieval. Under EVAL_NOFUNCEVAL field-only templates still resolve,
while getter/method templates fall back.
The metadata side adds an ECMA-335 II.23.3 SerString decoder and
GetAttributeFixedStringArgument() on top of ForEachAttribute().
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
|
ignore, I was just testing the infamous fable 5 |
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.
Adds
DebuggerDisplayAttribute.Valuesupport for class and struct variable display, part of the roadmap for #226 (previously requested in #142).What it does
A type annotated with
[DebuggerDisplay("Id = {Id}, Name = {GetName()}")]displays asId = 101, Name = "abc"in locals, watch, child variables and evaluate responses, for both MI and DAP. Lookup uses the exact runtime type with base-type fallback, and the attribute takes precedence over an overriddenToString()(matching Visual Studio).Supported placeholder subset (v1, by design):
{Field},{Prop},{GetText()}{A.B},this/this.Member, and the,nqmodifier{{}}, any literal textAnything else (method arguments, operators, indexers, format specifiers) conservatively falls back to the existing
{TypeName}display — a bad template never breaks variable retrieval. Explicit non-goals for now:Name/Typeattribute properties, assembly-levelTarget,DebuggerTypeProxy.How it works
attributes.cpp: ECMA-335 II.23.3 SerString decoder +GetAttributeFixedStringArgument()on top ofForEachAttribute()from refactor: centralize custom attribute blob handling #230.variables.cpp: template parser plus receiver-bound placeholder evaluation — members resolve viaEvaluator::ResolveIdentifiersand methods viaEvaluator::WalkMethods+EvalHelpers::EvalFunctionagainst the displayed value itself, not by re-evaluating the variable'sevaluateNametext, so watch expressions with side effects are never re-executed and children without a usableevaluateNamestill get display.PrintValue(no nested DebuggerDisplay/ToString), so display evaluation cannot recurse.EVAL_NOFUNCEVAL, field-only templates still resolve; templates needing a getter/method call fall back to{TypeName}.Testing
Extended MITestVariables and VSCodeTestVariables: field/property/method/mixed templates, struct, base-class attribute inheritance, custom generic type, BCL
List<int>/Dictionary<string,int>(Count = {Count}), literal-only, escaped braces, nested{A.B},,nq, missing-member and malformed-template fallback, precedence overToString(), child expansion, andEVAL_NOFUNCEVALbehavior.Full test suite run locally on this branch (Linux x64, .NET 10): 65/65 passed.
🤖 Generated with Claude Code