-
Notifications
You must be signed in to change notification settings - Fork 27.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor core uses of FlutterError. #30983
Conversation
1791396
to
2b717a3
Compare
This CL has only the core changes for the FlutterError refactor. Follow up CLs apply the refactor to more places. The diff is still large but take heart that a lot of the additions are just the text rendering logic and golden style tests in diagnosticable.dart. Two follow up CLs will apply the refactor to more places. |
ff14f3f
to
3439a0a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of nits but looking great. 👍
/// include hash codes. | ||
/// | ||
/// A [FlutterError] must start with an [ErrorSummary] and may not contain | ||
/// multiple summaries. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this line needs updating?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry can you clarify what about this line should be updated? It is still technically correct that every FlutterError starts with an ErrorSummary although sometimes the ErrorSummary is inferred parsing the text of the message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I wasn't clear. I just wondered if you wanted to remove the reference to the ErrorSummary
type in favor of a string since that's what folks will be passing in. A not really so no worries either way!
@@ -1563,7 +1628,7 @@ mixin WidgetInspectorService { | |||
|
|||
List<Object> _getProperties(String diagnosticsNodeId, String groupName) { | |||
final DiagnosticsNode node = toObject(diagnosticsNodeId); | |||
return _nodesToJson(node == null ? const <DiagnosticsNode>[] : node.getProperties(), _SerializeConfig(groupName: groupName)); | |||
return _nodesToJson(node == null ? const <DiagnosticsNode>[] : node.getProperties(), _SerializeConfig(groupName: groupName), parent: null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should parent be node
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
4bb7820
to
0cfbf7a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked at the stuff in foundation
. Seems mostly fine with some nits.
/// | ||
/// {@tool sample} | ||
/// ```dart | ||
/// _ErrorDiagnostic('Element $element must be $color') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this _ErrorDiagnostic constructor defined anywhere? Is this supposed to be a placeholder for ErrorSummary, ErrorDetails or ErrorHint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worthwhile to point that out or use a concrete example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and added a default constructor for this class.
/// and other callbacks that collect information describing an error. | ||
typedef InformationCollector = Iterable<DiagnosticsNode> Function(); | ||
|
||
class _ErrorDiagnostic extends DiagnosticsProperty<List<Object>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an abstract class? Or is it intended to be instantiated directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've marked it as abstract to clarify that.
/// information, etc. | ||
/// * [FlutterError], which is the most common place to use an [ErrorHint]. | ||
class ErrorHint extends _ErrorDiagnostic { | ||
/// A lint enforces that this constructor can only be called with a string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why's this not a limitation for the other Error* classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The limitation should apply to all these classes. Updated the documentation in all 3 similar classes to reflect that.
|
||
/// Returns a short (one line) description of the problem that was detected. | ||
/// | ||
/// If the exception contains an [ErrorSummary] that summary is used otherwise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not 100% sure: Should there be some kind of punctuation before otherwise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a comma
/// Render the tree on a single line without showing children. | ||
/// | ||
/// See also: | ||
/// * [FlutterErrorDetails], which uses this style to display the header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing blank line after "See also:"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
/// describing the context where an error occurred. | ||
headerLine, | ||
|
||
/// Render the tree on a single line with the name and value on separate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems oxymoronic. How can it be a single line if name and value are presented on separate lines, which makes it at least two lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes that is a bad name.
some alternate ideas:
- whitespaceNoChildren <-- that is what it is
- errorProperty <-- that is what it is for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed this. I've renamed this to errorProperty as that is what it is.
@@ -178,6 +235,13 @@ class TextTreeConfiguration { | |||
/// Prefix to add to the first line to display a child with this style. | |||
final String prefixLineOne; | |||
|
|||
/// Similar to prefixLineOne but applies even when this is the root node in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear for me what this does based on the description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the comment and moved this next to the related afterName property.
@@ -495,6 +638,41 @@ final TextTreeConfiguration whitespaceTextConfiguration = TextTreeConfiguration( | |||
isBlankLineBetweenPropertiesAndChildren: false, | |||
); | |||
|
|||
/// Whitespace only configuration where children are consistently indented |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are the two spaces coming from? The example below shows everything at the same indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no indentation. Clearly I started with the comment from the whitespace style and failed to update it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment was wrong. Fixed it. All the example diagrams were carefully constructed but some comments were out of sync with the content.
prefixOtherLinesRootNode: '', | ||
); | ||
|
||
/// Render a node as a single line omitting children styling the node like it is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be broken up in more than one runaway sentence to make it clearer what this does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've instead eliminated this style as the cases it was helping with are now handled by ErrorDescription instead which allows concatenating together strings and values to look like a header.
propertyPrefixIfChildren: '', | ||
propertyPrefixNoChildren: '', | ||
linkCharacter: '', | ||
prefixOtherLinesRootNode: '', | ||
); | ||
/// Render a node as a single line omitting children. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example below has two lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Updated the description and renamed to
errorProperty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the description to match the example.
304560a
to
a5fd4c6
Compare
@@ -16,7 +16,7 @@ void main() { | |||
handleUncaughtError:(Zone zone, ZoneDelegate delegate, Zone parent, Object error, StackTrace stackTrace) { | |||
FlutterError.reportError(FlutterErrorDetails( | |||
exception: error, | |||
context: 'In the Zone handleUncaughtError handler', | |||
context: ErrorDescription('In the Zone handleUncaughtError handler'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this message is saying, but since it's the same as it was before. Let's land it for now and revise later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm putting together a list of burndown errors to go after after this CL lands. I've added this to the list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the context
is just telling you where the error occurred, not what the error is (that's error
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct. Removed this one from the burndown list.
/// An [ErrorHint] provides specific, non-obvious advice that may be applicable. | ||
/// | ||
/// If your message provides obvious advice that is always applicable it is an | ||
/// [ErrorDescription] not a hint. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the advice is obvious from the summary/description of the error, it probably should not be included in the message? I feel this line is a bit extranous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like having the advice duplicated here as a reminder to only use hints for things that meet the fairly narrow charter of what a hint is.
'The $runtimeType sending notification was', | ||
this, | ||
style: DiagnosticsTreeStyle.errorProperty, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the ErrorSummary for this error, since it's not specified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exception is the piece that is expected to contain an ErrorSummary.
If the exception is a FlutterError we have assertions if it doesn't have an ErrorSummary.
We could be smart and if the exception is just a String, treat that String as an ErrorSummary.
@@ -524,27 +527,27 @@ class BoxConstraints extends Constraints { | |||
} else { | |||
whichFields = affectedFieldsList.single; | |||
} | |||
throwError('BoxConstraints has ${affectedFieldsList.length == 1 ? 'a NaN value' : 'NaN values' } in $whichFields.'); | |||
throwError(ErrorSummary('BoxConstraints has ${affectedFieldsList.length == 1 ? 'a NaN value' : 'NaN values' } in $whichFields.')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should spell out 'NaN' to make the message more beginner friendly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added that to the list of items to cleanup with a followup CL.
if (owner != null && owner.debugDoingLayout) | ||
hint = 'Only the object itself can set its size. It is a contract violation for other objects to set it.'; | ||
information.add(ErrorHint('Only the object itself can set its size. It is a contract violation for other objects to set it.')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one looks more like a description. It backs up the previous statement. A hint should suggest a specific way to get out of the error state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I was on the fence about that one. I only had it as a "hint" as the variable name was called "hint" in the previous code.
While auditing this myself I set this as a ErrorDescription and then moved it back to a hint.
ErrorHint( | ||
'This probably means that it is a render object that tries to be ' | ||
'as big as possible, but it was put inside another render object ' | ||
'that allows its children to pick their own size.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an ErrorDescription, too. To add a hint, what advice could we give someone who runs into this error? Would something like "try giving a finite size to the render object [link to documentation]" be useful as a hint here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to a description. Added to my list of things that could be nice to have a hint.
'$runtimeType object was given an infinite size during layout.\n' | ||
final List<DiagnosticsNode> errorParts = <DiagnosticsNode>[]; | ||
errorParts.add(ErrorSummary('$runtimeType object was given an infinite size during layout.')); | ||
errorParts.add(ErrorHint( | ||
'This probably means that it is a render object that tries to be ' | ||
'as big as possible, but it was put inside another render object ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed this message is the same as the one in L1732. I'm not sure if this is a problem or not. Please see my comment there about whether this qualifies as a hint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Downgraded this one as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also noted that it would be nice to dedupe the text.
errorParts.addAll(information); | ||
errorParts.add(DiagnosticsProperty<BoxConstraints>('The constraints that applied to the $runtimeType were', constraints, style: DiagnosticsTreeStyle.errorProperty)); | ||
errorParts.add(DiagnosticsProperty<Size>('The exact size it was given was', _size, style: DiagnosticsTreeStyle.errorProperty)); | ||
errorParts.add(ErrorHint('See https://flutter.io/layout/ for more information.')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very clearly a hint, though probably not specific enough. The new canonical URL for this doc is https://flutter.dev/docs/development/ui/layout/box-constraints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated the doc link. done.
'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=BUG.md' | ||
); | ||
throw FlutterError.fromParts(<DiagnosticsNode>[ | ||
ErrorSummary('$runtimeType does not meet its constraints.'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A side question: will the user be able to locate this particular widget in their code, especially when there are many instances of this type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They will be as soon as the kernel transformer for ErrorSummary is enabled, click on the type name in the UI will lead you to the RenderObject matching the type in the inspector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
@@ -232,18 +239,22 @@ mixin DebugOverflowIndicatorMixin on RenderObject { | |||
overflows[overflows.length - 1] = 'and ${overflows[overflows.length - 1]}'; | |||
overflowText = overflows.join(', '); | |||
} | |||
// TODO(jacobr): add the overflows in pixels as structured data so they can | |||
// be visualized in debugging tools. | |||
FlutterError.reportError( | |||
FlutterErrorDetailsForRendering( | |||
exception: 'A $runtimeType overflowed by $overflowText.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this line be the error's summary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow. We shouldn't have exceptions that are strings. @pq we should probably add a lint to catch this case although it is low priority because the good news is this is the only place in the framework where that mistake was made.
Fixed this to construct a proper FlutterError object with a summary. The reason for this bug is the Dart type system lacks a single base class for all exceptions.
'The edge of the $runtimeType that is ' | ||
'overflowing has been marked in the rendering with a yellow and black ' | ||
'striped pattern. This is usually caused by the contents being too big ' | ||
'for the $runtimeType.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd consider this part as an ErrorDescription.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
'is content that cannot be seen. If the content is legitimately bigger ' | ||
'than the available space, consider clipping it with a ClipRect widget ' | ||
'before putting it in the $runtimeType, or using a scrollable ' | ||
'container, like a ListView.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this part is a hint.
..add(ErrorHint( | ||
'If none of the above helps enough to fix this problem, please don\'t hesitate to file a bug:\n' | ||
' https://github.com/flutter/flutter/issues/new?template=BUG.md' | ||
))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This last part is neither a hint nor a description, following our definitions. I suggest we treat this as a description for now to avoid drawing undeserving attention to it. We should probably track how often this error reporting link is collected, maybe we can set up a URL redirect via flutter.dev.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Made it a description.
'The edge of the $runtimeType that is overflowing has been marked ' | ||
'in the rendering with a yellow and black striped pattern. This is ' | ||
'usually caused by the contents being too big for the $runtimeType. ' | ||
'Consider applying a flex factor (e.g. using an Expanded widget) to ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hint starts from this line. I'd consider the three lines above it a description of the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
if (owner != null && owner.debugDoingLayout) | ||
hint = 'Only the object itself can set its geometry. It is a contract violation for other objects to set it.'; | ||
hint = ErrorHint('Only the object itself can set its geometry. It is a contract violation for other objects to set it.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd consider this a description, since it's unclear what the user should try to resolve the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Hixie it looks like you wrote the line that originally gave this the variable name hint
.
Do you think it should be called a hint with the current definition of what a hint is?
To make it clearer what action to take we could write something a bit redundant like.
Look for where the geometry is being set outside outside the object refactor the logic to set the geometry from inside the object.
For now set this as an ErrorDescription as
hint = ErrorDescription(
is a great breadcrumb to later go back and cleanup ErrorDescription objects.
if (collidingGuarder.className == null && collidingGuarder.methodName == 'expect') { | ||
message.writeln( | ||
information.add(ErrorHint( | ||
'If you are confident that all test APIs are being called using "await", and ' | ||
'this expect() call is not being called at the top level but is itself being ' | ||
'called from some sort of callback registered before the ${originalGuarder.methodName} ' | ||
'method was called, then consider using expectSync() instead.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this is a hint.
Looks good to me. Thanks! |
ff43c2d
to
ceed498
Compare
All comments have been addressed. Please take another look. |
// Examples can assume: | ||
// dynamic element; | ||
// dynamic color; | ||
// dynamic _ErrorDiagnostic; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this makes it visible to all dartdocs everywhere. Since the relevant class is private anyway, might be worth making it //
docs instead of ///
docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Made the problem comment a // comment as it isn't for public docs anyway.
Description
Make FlutterError objects more structured so they can be displayed better in debugging tools such as Dart DevTools.
Related Issues
#27327
Tests
Most tests pass as is as the text output does not change as the refactor does not break existing text based error displays.
New test cases are added in flutter_test to verify the structure is maintained and new tests are added to diagnostics_test.dart to verify that the additional text formatting logic works as expected.
Other test cases are updated for cases where the output changed in way that is minor and either completely neutral or positive.
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.///
).flutter analyze --flutter-repo
) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?