diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 44b6fdb322..359802c63d 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -2668,6 +2668,30 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { /// /// Provides a child model for a render object subclass that has a unique child. abstract class RenderObjectWithChildMixin implements RenderObject { + bool debugValidateChild(RenderObject child) { + assert(() { + if (child is! ChildType) { + throw new FlutterError( + 'A $runtimeType expected a child of type $ChildType but received a ' + 'child of type ${child.runtimeType}.\n' + 'RenderObjects expect specific types of children because they ' + 'coordinate with their children during layout and paint. For ' + 'example, a RenderSliver cannot be the child of a RenderBox because ' + 'a RenderSliver does not understand the RenderBox layout protocol.\n' + '\n' + 'The $runtimeType that expected a $ChildType child was created by:\n' + ' $debugCreator\n' + '\n' + 'The ${child.runtimeType} that did not match the expected child type ' + 'was created by:\n' + ' ${child.debugCreator}\n' + ); + } + return true; + }); + return true; + } + ChildType _child; /// The render object's unique child ChildType get child => _child; @@ -2770,6 +2794,30 @@ abstract class ContainerRenderObjectMixin _childCount; + bool debugValidateChild(RenderObject child) { + assert(() { + if (child is! ChildType) { + throw new FlutterError( + 'A $runtimeType expected a child of type $ChildType but received a ' + 'child of type ${child.runtimeType}.\n' + 'RenderObjects expect specific types of children because they ' + 'coordinate with their children during layout and paint. For ' + 'example, a RenderSliver cannot be the child of a RenderBox because ' + 'a RenderSliver does not understand the RenderBox layout protocol.\n' + '\n' + 'The $runtimeType that expected a $ChildType child was created by:\n' + ' $debugCreator\n' + '\n' + 'The ${child.runtimeType} that did not match the expected child type ' + 'was created by:\n' + ' ${child.debugCreator}\n' + ); + } + return true; + }); + return true; + } + ChildType _firstChild; ChildType _lastChild; void _insertIntoChildList(ChildType child, { ChildType after }) { diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 7cce157b41..3d45b31310 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -599,6 +599,7 @@ class RenderObjectToWidgetElement extends RootRenderObje @override void insertChildRenderObject(RenderObject child, dynamic slot) { assert(slot == _rootChildSlot); + assert(renderObject.debugValidateChild(child)); renderObject.child = child; } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 10f0b35f9e..550f84129f 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -4096,6 +4096,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement { void insertChildRenderObject(RenderObject child, dynamic slot) { final RenderObjectWithChildMixin renderObject = this.renderObject; assert(slot == null); + assert(renderObject.debugValidateChild(child)); renderObject.child = child; assert(renderObject == this.renderObject); } @@ -4144,6 +4145,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement { @override void insertChildRenderObject(RenderObject child, Element slot) { final ContainerRenderObjectMixin> renderObject = this.renderObject; + assert(renderObject.debugValidateChild(child)); renderObject.insert(child, after: slot?.renderObject); assert(renderObject == this.renderObject); } diff --git a/packages/flutter/lib/src/widgets/layout_builder.dart b/packages/flutter/lib/src/widgets/layout_builder.dart index 781da38869..6e476b3bc3 100644 --- a/packages/flutter/lib/src/widgets/layout_builder.dart +++ b/packages/flutter/lib/src/widgets/layout_builder.dart @@ -131,6 +131,7 @@ class _LayoutBuilderElement extends RenderObjectElement { void insertChildRenderObject(RenderObject child, dynamic slot) { final RenderObjectWithChildMixin renderObject = this.renderObject; assert(slot == null); + assert(renderObject.debugValidateChild(child)); renderObject.child = child; assert(renderObject == this.renderObject); } diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 9d26c8313b..ab2d763856 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -422,6 +422,7 @@ class _TheatreElement extends RenderObjectElement { @override void insertChildRenderObject(RenderBox child, dynamic slot) { + assert(renderObject.debugValidateChild(child)); if (slot == _onstageSlot) { assert(child is RenderStack); renderObject.child = child; diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart index febbaaef62..628dc190d7 100644 --- a/packages/flutter/lib/src/widgets/sliver.dart +++ b/packages/flutter/lib/src/widgets/sliver.dart @@ -647,6 +647,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render void insertChildRenderObject(covariant RenderObject child, int slot) { assert(slot != null); assert(_currentlyUpdatingChildIndex == slot); + assert(renderObject.debugValidateChild(child)); renderObject.insert(child, after: _currentBeforeChild); assert(() { final SliverMultiBoxAdaptorParentData childParentData = child.parentData; diff --git a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart index 1c088f4e3e..fe9346cedc 100644 --- a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart @@ -125,6 +125,7 @@ class _SliverPersistentHeaderElement extends RenderObjectElement { @override void insertChildRenderObject(covariant RenderObject child, Null slot) { + assert(renderObject.debugValidateChild(child)); renderObject.child = child; } diff --git a/packages/flutter/test/widgets/box_sliver_mismatch_test.dart b/packages/flutter/test/widgets/box_sliver_mismatch_test.dart new file mode 100644 index 0000000000..139e30e557 --- /dev/null +++ b/packages/flutter/test/widgets/box_sliver_mismatch_test.dart @@ -0,0 +1,58 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +void main() { + testWidgets('Sliver in a box', (WidgetTester tester) async { + await tester.pumpWidget( + new DecoratedBox( + decoration: const BoxDecoration(), + child: new SliverList( + delegate: const SliverChildListDelegate(const []), + ), + ), + ); + + expect(tester.takeException(), isFlutterError); + + await tester.pumpWidget( + new Row( + children: [ + new SliverList( + delegate: const SliverChildListDelegate(const []), + ), + ], + ), + ); + + expect(tester.takeException(), isFlutterError); + }); + + testWidgets('Box in a sliver', (WidgetTester tester) async { + await tester.pumpWidget( + new CustomScrollView( + slivers: [ + const SizedBox(), + ], + ) + ); + + expect(tester.takeException(), isFlutterError); + + await tester.pumpWidget( + new CustomScrollView( + slivers: [ + const SliverPadding( + padding: EdgeInsets.zero, + sliver: const SizedBox(), + ), + ], + ) + ); + + expect(tester.takeException(), isFlutterError); + }); +}