Track if location has access to this.

We will need this also to implement '16.35 Lexical Lookup' from the spec.

Change-Id: I3b77c4a016fd54771b07957d522074aeb8dd1938
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/158802
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 1576546..512d356 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -39,6 +39,7 @@
 import 'package:analyzer/src/generated/java_engine.dart';
 import 'package:analyzer/src/generated/parser.dart' show ParserErrorCode;
 import 'package:analyzer/src/generated/sdk.dart' show DartSdk, SdkLibrary;
+import 'package:analyzer/src/generated/this_access_tracker.dart';
 import 'package:analyzer/src/task/strong/checker.dart';
 import 'package:meta/meta.dart';
 
@@ -194,6 +195,9 @@
   /// in the scope of an extension.
   ExtensionElement _enclosingExtension;
 
+  /// The helper for tracking if the current location has access to `this`.
+  final ThisAccessTracker _thisAccessTracker = ThisAccessTracker.unit();
+
   /// The context of the method or function that we are currently visiting, or
   /// `null` if we are not inside a method or function.
   EnclosingExecutableContext _enclosingExecutable =
@@ -366,6 +370,16 @@
   }
 
   @override
+  void visitBlockFunctionBody(BlockFunctionBody node) {
+    _thisAccessTracker.enterFunctionBody(node);
+    try {
+      super.visitBlockFunctionBody(node);
+    } finally {
+      _thisAccessTracker.exitFunctionBody(node);
+    }
+  }
+
+  @override
   void visitBreakStatement(BreakStatement node) {
     SimpleIdentifier labelNode = node.label;
     if (labelNode != null) {
@@ -546,8 +560,13 @@
 
   @override
   void visitExpressionFunctionBody(ExpressionFunctionBody node) {
-    _returnTypeVerifier.verifyExpressionFunctionBody(node);
-    super.visitExpressionFunctionBody(node);
+    _thisAccessTracker.enterFunctionBody(node);
+    try {
+      _returnTypeVerifier.verifyExpressionFunctionBody(node);
+      super.visitExpressionFunctionBody(node);
+    } finally {
+      _thisAccessTracker.exitFunctionBody(node);
+    }
   }
 
   @override
@@ -574,6 +593,7 @@
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
     var fields = node.fields;
+    _thisAccessTracker.enterFieldDeclaration(node);
     _isInStaticVariableDeclaration = node.isStatic;
     _isInInstanceNotLateVariableDeclaration =
         !node.isStatic && !node.fields.isLate;
@@ -591,6 +611,7 @@
     } finally {
       _isInStaticVariableDeclaration = false;
       _isInInstanceNotLateVariableDeclaration = false;
+      _thisAccessTracker.exitFieldDeclaration(node);
     }
   }
 
@@ -2976,7 +2997,7 @@
   ///
   /// See [CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS].
   void _checkForInvalidReferenceToThis(ThisExpression expression) {
-    if (!_isThisInValidContext(expression)) {
+    if (!_thisAccessTracker.hasAccess) {
       _errorReporter.reportErrorForNode(
           CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS, expression);
     }
@@ -5161,29 +5182,6 @@
     return false;
   }
 
-  /// Return `true` if the given 'this' [expression] is in a valid context.
-  bool _isThisInValidContext(ThisExpression expression) {
-    for (AstNode node = expression.parent; node != null; node = node.parent) {
-      if (node is CompilationUnit) {
-        return false;
-      } else if (node is ConstructorDeclaration) {
-        return node.factoryKeyword == null;
-      } else if (node is ConstructorInitializer) {
-        return false;
-      } else if (node is MethodDeclaration) {
-        return !node.isStatic;
-      } else if (node is FieldDeclaration) {
-        if (node.fields.isLate &&
-            (node.parent is ClassDeclaration ||
-                node.parent is MixinDeclaration)) {
-          return true;
-        }
-        // Continue; a non-late variable may still occur in a valid context.
-      }
-    }
-    return false;
-  }
-
   /// Return `true` if the given [identifier] is in a location where it is
   /// allowed to resolve to a static member of a supertype.
   bool _isUnqualifiedReferenceToNonLocalStaticMemberAllowed(
diff --git a/pkg/analyzer/lib/src/generated/this_access_tracker.dart b/pkg/analyzer/lib/src/generated/this_access_tracker.dart
new file mode 100644
index 0000000..7fdb8d3
--- /dev/null
+++ b/pkg/analyzer/lib/src/generated/this_access_tracker.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. 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:analyzer/dart/ast/ast.dart';
+
+/// Tracks if the current location has access to `this`.
+///
+/// The current instance (and hence its members) can only be accessed at
+/// specific locations in a class: We say that a location `l` has access to
+/// `this` iff `l` is inside the body of a declaration of an instance member,
+/// or a generative constructor, or in the initializing expression of a `late`
+/// instance variable declaration.
+class ThisAccessTracker {
+  final List<bool> _stack = [];
+
+  ThisAccessTracker.unit() {
+    _stack.add(false);
+  }
+
+  bool get hasAccess => _stack.last;
+
+  void enterFieldDeclaration(FieldDeclaration node) {
+    _stack.add(!node.isStatic && node.fields.isLate);
+  }
+
+  void enterFunctionBody(FunctionBody node) {
+    var parent = node.parent;
+    if (parent is ConstructorDeclaration) {
+      _stack.add(parent.factoryKeyword == null);
+    } else if (parent is MethodDeclaration) {
+      _stack.add(!parent.isStatic);
+    } else {
+      _stack.add(_stack.last);
+    }
+  }
+
+  void exitFieldDeclaration(FieldDeclaration node) {
+    _stack.removeLast();
+  }
+
+  void exitFunctionBody(FunctionBody node) {
+    _stack.removeLast();
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_reference_to_this_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_reference_to_this_test.dart
index 48725a2..d565790 100644
--- a/pkg/analyzer/test/src/diagnostics/invalid_reference_to_this_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/invalid_reference_to_this_test.dart
@@ -16,7 +16,7 @@
 
 @reflectiveTest
 class InvalidReferenceToThisTest extends PubPackageResolutionTest {
-  test_constructor_valid() async {
+  test_class_constructor() async {
     await assertErrorsInCode(r'''
 class A {
   A() {
@@ -28,7 +28,7 @@
     ]);
   }
 
-  test_factoryConstructor() async {
+  test_class_factoryConstructor() async {
     await assertErrorsInCode(r'''
 class A {
   factory A() { return this; }
@@ -38,19 +38,17 @@
     ]);
   }
 
-  test_instanceMethod_valid() async {
-    await assertErrorsInCode(r'''
+  test_class_instanceMethod() async {
+    await assertNoErrorsInCode(r'''
 class A {
-  m() {
-    var v = this;
+  void foo() {
+    this;
   }
 }
-''', [
-      error(HintCode.UNUSED_LOCAL_VARIABLE, 26, 1),
-    ]);
+''');
   }
 
-  test_instanceVariableInitializer_inConstructor() async {
+  test_class_instanceVariableInitializer_inConstructor() async {
     await assertErrorsInCode(r'''
 class A {
   var f;
@@ -61,7 +59,7 @@
     ]);
   }
 
-  test_instanceVariableInitializer_inDeclaration() async {
+  test_class_instanceVariableInitializer_inDeclaration() async {
     await assertErrorsInCode(r'''
 class A {
   var f = this;
@@ -71,7 +69,7 @@
     ]);
   }
 
-  test_staticMethod() async {
+  test_class_staticMethod() async {
     await assertErrorsInCode(r'''
 class A {
   static m() { return this; }
@@ -81,7 +79,7 @@
     ]);
   }
 
-  test_staticVariableInitializer() async {
+  test_class_staticVariableInitializer() async {
     await assertErrorsInCode(r'''
 class A {
   static A f = this;
@@ -91,7 +89,7 @@
     ]);
   }
 
-  test_superInitializer() async {
+  test_class_superInitializer() async {
     await assertErrorsInCode(r'''
 class A {
   A(var x) {}
@@ -112,25 +110,13 @@
     ]);
   }
 
-  test_variableInitializer() async {
+  test_topLevelVariable() async {
     await assertErrorsInCode('''
 int x = this;
 ''', [
       error(CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS, 8, 4),
     ]);
   }
-
-  test_variableInitializer_inMethod_notLate() async {
-    await assertErrorsInCode(r'''
-class A {
-  f() {
-    var r = this;
-  }
-}
-''', [
-      error(HintCode.UNUSED_LOCAL_VARIABLE, 26, 1),
-    ]);
-  }
 }
 
 @reflectiveTest