Skip to content

String interpolation that captures object references for debugging tools #27673

Closed as not planned

Description

@jacob314

String interpolation in Dart is a convenient way to write a debug string but it has the limitation that the output can only be a string with no way for debuggers to extract out the actual objects referenced to generate richer debugger view and crosslink to other debugging tools such as the widget inspector.

For example, on the web you could write

console.log('Element ', element, ' was clicked');

and have the element show as an interactive element that you could click on to view in the elements page of the chrome devtools.
If you instead wrote

print('Element $element was clicked');

you would get a non interactive string representation of the Element. However, string interpolation is far easier to read than a list of optional positional parameters separated by commas so it is far preferable to use string interpolation if the needed structure can be maintained.

Proposal: write a kernel transformer and a lint that operate on a blessed list of constructors and top level methods in package:flutter, extracting the object references out of the expressions and encoding the output as the existing flexible DiagnosticsNode structure already used by Flutter debugging tools. The toString() of the DiagnosticsNode will match the toString of the string literal but the DiagnosticsNode will also contain the list of all object references in the string interpolation expression. The constructors will still function without the transformer but will not capture any of the object references if run in that mode in a release build. Users cannot attach debuggers to release builds anyway.

A lint warning will trigger if the constructor is called with a value that is not a string literal as in that case there is no way to robustly extract out the object ids and we will not attempt to be clever and handle any cases where the value passed to the constructor is more than a string literal.

Example:

class ErrorMessage extends DiagnosticsNode {
   
   // Calls to this constructor are replaced in debug mode by a kernel transformer which captures
   // the contents of the string interpolation template. In release mode this object is identical to
   // Diagnostics.message(message).
   ErrorMessage(String message);
}
ErrorMessage('Element $element is color $color'); // Good. Captures references to the element and color object for use by debugging tools.
final String str = 'Element $element is color $color';
ErrorMessage(str); // lint error
ErrorMessage('Element $element' + 'is color $color'); // lint error because want to keep things simple.
ErrorMessage('Element $element'
                         'is color $color'); // good.

// Hypothetical method that logs objects to the console and to the console in IDEs.

debugLog(('Element $element is color $color'); // good
final String str = 'Element $element is color $color';
debugLog(str); // lint error

Function foo = debugLog; // lint warning as otherwise there could be calls to debugLog that we can't 
foo(str); // protected by the lint warning on the previous line.

If Dart string interpolation was a more powerful string templating feature this kernel transformer wouldn't be needed but given the current Dart language it is the most practical way to support the sorts of minimal string templates handy for debugging.

More complex examples:

    ErrorMessage('Curves must map 0.0 to near zero and 1.0 to near one but '
              '${activeCurve.runtimeType} mapped $t to $transformedValue, which '
              'is near $roundedTransformedValue.'

This example suggests the heuristic that we should treat foo.runtimeType as capturing an object reference to foo instead of foo.runtimeType as the runtimeType call was added to make the regular string output prettier.
We may also want to generate lint errors on cases that are ambiguous to resolve at first so we don't over promise what templates we can robustly handle.
For example:

ErrorMessage('Button is ${button.onClick != null ? 'enabled' :  'disabled'}');

might be a lint error at first as we are unclear what object reference to include. Showing a reference to the onClick event handler might be surprising or it might be what the error message author expected.

Additional considerations: for some cases debugging tools will want to display a richer debugging view of the object inline. This should only be done if the richer object view can generally fit in a similar sized area as the toString such as in the case of an icon or a color. For cases where the visual debug view is much larger, additional techniques need to be considered to evaluate how to display the visual view of the object adjacent to the message.

See #27327
for a related flutter issue that motivated this bug.

Open questions:
Should special patterns like

ErrorMessage(
  'Some curve:'
  '  $curve'
);'

be treated as "markdown" syntax for fully opting into property style view of the object?
Essentially that expression could be interpreted as meaning DiagnosticsProperty('Some curvet', curve)
which opens up opting into all rich debugger views of an object rather than being limited the subset of debugger views of objects that place nicely with inline display.

For example:
This would be the way a curve property could be displayed if we are confident we should opt into a full property view: which ignoring my poor markdown skills would look something like this:
Some curve: image

Activity

  1. added
    toolAffects the "flutter" command-line tool. See also t: labels.
    engineflutter/engine related. See also e: labels.
    on Feb 8, 2019
  2. added this to the milestone on Feb 8, 2019
  3. added
    P2Important issues not at the top of the work list
    on May 29, 2020
  4. removed this from the milestone on Aug 17, 2020
  5. github-actions commented on Mar 13, 2025

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  6. locked as resolved and limited conversation to collaborators on Mar 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: new featureNothing broken; request for a new capabilitydependency: dartDart team may need to help usengineflutter/engine related. See also e: labels.team-engineOwned by Engine teamtoolAffects the "flutter" command-line tool. See also t: labels.triaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions