Error message improvements (#58204)

This commit is contained in:
Ian Hickson 2020-06-02 18:03:03 -07:00 committed by GitHub
parent 1a9530af8a
commit 67881172fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 4 deletions

View file

@ -525,7 +525,7 @@ class AlignmentDirectional extends AlignmentGeometry {
@override
Alignment resolve(TextDirection direction) {
assert(direction != null);
assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.');
switch (direction) {
case TextDirection.rtl:
return Alignment(-start, y);
@ -621,7 +621,7 @@ class _MixedAlignment extends AlignmentGeometry {
@override
Alignment resolve(TextDirection direction) {
assert(direction != null);
assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.');
switch (direction) {
case TextDirection.rtl:
return Alignment(_x - _start, _y);

View file

@ -2627,7 +2627,34 @@ class BuildOwner {
while (index < dirtyCount) {
assert(_dirtyElements[index] != null);
assert(_dirtyElements[index]._inDirtyList);
assert(!_dirtyElements[index]._active || _dirtyElements[index]._debugIsInScope(context));
assert(() {
if (_dirtyElements[index]._active && !_dirtyElements[index]._debugIsInScope(context)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Tried to build dirty widget in the wrong build scope.'),
ErrorDescription(
'A widget which was marked as dirty and is still active was scheduled to be built, '
'but the current build scope unexpectedly does not contain that widget.',
),
ErrorHint(
'Sometimes this is detected when an element is removed from the widget tree, but the '
'element somehow did not get marked as inactive. In that case, it might be caused by '
'an ancestor element failing to implement visitChildren correctly, thus preventing '
'some or all of its descendants from being correctly deactivated.',
),
DiagnosticsProperty<Element>(
'The root of the build scope was',
context,
style: DiagnosticsTreeStyle.errorProperty,
),
DiagnosticsProperty<Element>(
'The offending element (which does not appear to be a descendant of the root of the build scope) was',
_dirtyElements[index],
style: DiagnosticsTreeStyle.errorProperty,
),
]);
}
return true;
}());
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {

View file

@ -1427,6 +1427,74 @@ void main() {
expect(debugDoingBuildOnDidUnmountRenderObject, isFalse);
});
});
testWidgets('A widget whose element has an invalid visitChildren implementation triggers a useful error message', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
await tester.pumpWidget(Container(child: _WidgetWithNoVisitChildren(_StatefulLeaf(key: key))));
(key.currentState as _StatefulLeafState).markNeedsBuild();
await tester.pumpWidget(Container());
final dynamic exception = tester.takeException();
expect(
exception.message,
equalsIgnoringHashCodes(
'Tried to build dirty widget in the wrong build scope.\n'
'A widget which was marked as dirty and is still active was scheduled to be built, '
'but the current build scope unexpectedly does not contain that widget.\n'
'Sometimes this is detected when an element is removed from the widget tree, but '
'the element somehow did not get marked as inactive. In that case, it might be '
'caused by an ancestor element failing to implement visitChildren correctly, thus '
'preventing some or all of its descendants from being correctly deactivated.\n'
'The root of the build scope was:\n'
' [root]\n'
'The offending element (which does not appear to be a descendant of the root of '
'the build scope) was:\n'
' _StatefulLeaf-[GlobalKey#00000]'
)
);
});
}
class _WidgetWithNoVisitChildren extends StatelessWidget {
const _WidgetWithNoVisitChildren(this.child, { Key key }) :
super(key: key);
final Widget child;
@override
Widget build(BuildContext context) => child;
@override
_WidgetWithNoVisitChildrenElement createElement() => _WidgetWithNoVisitChildrenElement(this);
}
class _WidgetWithNoVisitChildrenElement extends StatelessElement {
_WidgetWithNoVisitChildrenElement(_WidgetWithNoVisitChildren widget): super(widget);
@override
void visitChildren(ElementVisitor visitor) {
// This implementation is intentionally buggy, to test that an error message is
// shown when this situation occurs.
// The superclass has the correct implementation (calling `visitor(_child)`), so
// we don't call it here.
}
}
class _StatefulLeaf extends StatefulWidget {
const _StatefulLeaf({ Key key }) : super(key: key);
@override
State<_StatefulLeaf> createState() => _StatefulLeafState();
}
class _StatefulLeafState extends State<_StatefulLeaf> {
void markNeedsBuild() {
setState(() { });
}
@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}
class Decorate extends StatefulWidget {
@ -1503,7 +1571,6 @@ class NullChildElement extends Element {
bool get debugDoingBuild => throw UnimplementedError();
}
class DirtyElementWithCustomBuildOwner extends Element {
DirtyElementWithCustomBuildOwner(BuildOwner buildOwner, Widget widget)
: _owner = buildOwner, super(widget);