Error message improvements (#58204)

diff --git a/packages/flutter/lib/src/painting/alignment.dart b/packages/flutter/lib/src/painting/alignment.dart
index 3a72983..b5d6bc3 100644
--- a/packages/flutter/lib/src/painting/alignment.dart
+++ b/packages/flutter/lib/src/painting/alignment.dart
@@ -525,7 +525,7 @@
 
   @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 @@
 
   @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);
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index 7fe0ccf..2c895b7 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -2627,7 +2627,34 @@
       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) {
diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart
index 68bb488..80c890d 100644
--- a/packages/flutter/test/widgets/framework_test.dart
+++ b/packages/flutter/test/widgets/framework_test.dart
@@ -1427,6 +1427,74 @@
       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 @@
   bool get debugDoingBuild => throw UnimplementedError();
 }
 
-
 class DirtyElementWithCustomBuildOwner extends Element {
   DirtyElementWithCustomBuildOwner(BuildOwner buildOwner, Widget widget)
     : _owner = buildOwner, super(widget);