Commit 35b18c3d authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1062 from Hixie/better-asserts

Change how we provide additional information for asserts in the Widget framework.
parents 7bf3cb41 b1bd8017
...@@ -146,11 +146,11 @@ abstract class GlobalKey<T extends State<StatefulComponent>> extends Key { ...@@ -146,11 +146,11 @@ abstract class GlobalKey<T extends State<StatefulComponent>> extends Key {
static bool _debugCheckForDuplicates() { static bool _debugCheckForDuplicates() {
String message = ''; String message = '';
for (GlobalKey key in _debugDuplicates.keys) { for (GlobalKey key in _debugDuplicates.keys) {
message += 'Duplicate GlobalKey found amongst mounted elements: $key (${_debugDuplicates[key]} instances)\n'; message += 'The following GlobalKey was found multiple times among mounted elements: $key (${_debugDuplicates[key]} instances)\n';
message += 'Most recently registered instance is:\n${_registry[key]}\n'; message += 'The most recently registered instance is: ${_registry[key]}\n';
} }
if (!_debugDuplicates.isEmpty) if (!_debugDuplicates.isEmpty)
throw message; throw new WidgetError('Incorrect GlobalKey usage.', message);
return true; return true;
} }
...@@ -1155,12 +1155,10 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> { ...@@ -1155,12 +1155,10 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> {
built = _builder(this); built = _builder(this);
assert(() { assert(() {
if (built == null) { if (built == null) {
debugPrint('Widget: $widget'); throw new WidgetError(
assert(() { 'A build function returned null. Build functions must never return null.',
'A build function returned null. Build functions must never return null.' 'The offending widget is: $widget'
'The offending widget is displayed above.'; );
return false;
});
} }
return true; return true;
}); });
...@@ -1239,8 +1237,7 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1239,8 +1237,7 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
assert(() { assert(() {
if (_state._debugLifecycleState == _StateLifecycle.initialized) if (_state._debugLifecycleState == _StateLifecycle.initialized)
return true; return true;
debugPrint('${_state.runtimeType}.initState failed to call super.initState'); throw new WidgetError('${_state.runtimeType}.initState failed to call super.initState.');
return false;
}); });
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; }); assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
super._firstBuild(); super._firstBuild();
...@@ -1275,8 +1272,7 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1275,8 +1272,7 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
assert(() { assert(() {
if (_state._debugLifecycleState == _StateLifecycle.defunct) if (_state._debugLifecycleState == _StateLifecycle.defunct)
return true; return true;
debugPrint('${_state.runtimeType}.dispose failed to call super.dispose'); throw new WidgetError('${_state.runtimeType}.dispose failed to call super.dispose.');
return false;
}); });
assert(!dirty); // See BuildableElement.unmount for why this is important. assert(!dirty); // See BuildableElement.unmount for why this is important.
_state._element = null; _state._element = null;
...@@ -1333,14 +1329,12 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> { ...@@ -1333,14 +1329,12 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> {
} }
if (ancestor != null && badAncestors.isEmpty) if (ancestor != null && badAncestors.isEmpty)
return true; return true;
debugPrint(widget.debugDescribeInvalidAncestorChain( throw new WidgetError('Incorrect use of ParentDataWidget.', widget.debugDescribeInvalidAncestorChain(
description: "$this", description: "$this",
ownershipChain: parent.debugGetOwnershipChain(10), ownershipChain: parent.debugGetOwnershipChain(10),
foundValidAncestor: ancestor != null, foundValidAncestor: ancestor != null,
badAncestors: badAncestors badAncestors: badAncestors
)); ));
assert('Incorrect use of ParentDataWidget. See console log for details.' == true);
return true;
}); });
super.mount(parent, slot); super.mount(parent, slot);
} }
...@@ -1439,10 +1433,8 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1439,10 +1433,8 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// dirty, e.g. if they have a builder callback. (Builder callbacks have a // dirty, e.g. if they have a builder callback. (Builder callbacks have a
// 'BuildContext' argument which you can pass to Theme.of() and other // 'BuildContext' argument which you can pass to Theme.of() and other
// InheritedWidget APIs which eventually trigger a rebuild.) // InheritedWidget APIs which eventually trigger a rebuild.)
debugPrint('$runtimeType failed to implement reinvokeBuilders(), but got marked dirty');
assert(() { assert(() {
'reinvokeBuilders() not implemented'; throw new WidgetError('$runtimeType failed to implement reinvokeBuilders(), but got marked dirty.');
return false;
}); });
} }
...@@ -1742,7 +1734,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1742,7 +1734,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
continue; // when these nodes are reordered, we just reassign the data continue; // when these nodes are reordered, we just reassign the data
if (!idSet.add(child.key)) { if (!idSet.add(child.key)) {
throw 'If multiple keyed nodes exist as children of another node, they must have unique keys. $widget has multiple children with key "${child.key}".'; throw new WidgetError('If multiple keyed nodes exist as children of another node, they must have unique keys. $widget has multiple children with key "${child.key}".');
} }
} }
return false; return false;
...@@ -1771,6 +1763,18 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1771,6 +1763,18 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
} }
} }
class WidgetError extends Error {
WidgetError(String message, [ String rawDetails = '' ]) {
rawDetails = rawDetails.trimRight(); // remove trailing newlines
if (rawDetails != '')
_message = '$message\n$rawDetails';
else
_message = message;
}
String _message;
String toString() => _message;
}
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack); typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
/// This callback is invoked whenever an exception is caught by the widget /// This callback is invoked whenever an exception is caught by the widget
/// system. The 'context' argument is a description of what was happening when /// system. The 'context' argument is a description of what was happening when
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment