Adjust the lexical lookup

This CL adjusts the lexical lookup such that an instance member
declaration in the lexically enclosing syntax is reported using null for
the getter and for the setter result. This ensures that the search ends
(we shouldn't search the library scope in this situation), and it
ensures that the given member is handled in the same way as if the
search had exhausted all levels and found nothing, which will then cause
further processing to prepend `this.` and resolve the identifier as an
instance member.

This change aligns the implemented behavior to match the language
specification more precisely, and it is necessary in order to get the
desired and specified behavior with lookups that originate in an
anonymous method.

Change-Id: Ic11c412b91827d57455a1e7febbb031b5aadad70
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459740
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Erik Ernst <eernst@google.com>
This commit is contained in:
Erik Ernst
2025-11-19 09:20:44 -08:00
committed by Commit Queue
parent 95e1bb4479
commit 3af598a221
7 changed files with 189 additions and 106 deletions
@@ -274,6 +274,37 @@ class InstanceScope extends EnclosedScope {
element.setters.forEach(_addSetter);
element.methods.forEach(_addGetter);
}
@override
ScopeLookupResult lookup(String id) {
// When a lexical lookup encounters an instance member with the requested
// basename, the returned result should have null as the getter and setter
// (and that is the result, so we should not search the enclosing scope).
// This corresponds to the following rule from the specification:
// "Consider the case where D is an instance member declaration in a class
// or mixin A. The lexical lookup then yields nothing."
var getter = _getters[id];
var setter = _setters[id];
if (getter == null && setter == null) {
return _parent.lookup(id);
}
if (_isStatic(getter) || _isStatic(setter)) {
return ScopeLookupResultImpl(getter: getter, setter: setter);
}
// A declaration with the right basename was found, so the search ends.
// Return nulls to ensure that it is handled as with a prepended `this.`.
return ScopeLookupResultImpl(getter: null, setter: null);
}
static bool _isStatic(Element? element) {
switch (element) {
case PropertyAccessorElement(:var isStatic) ||
MethodElement(:var isStatic):
return isStatic;
default:
return false;
}
}
}
/// The top-level declarations of the library.
@@ -5017,7 +5017,9 @@ class C<T> {
MethodInvocation
methodName: SimpleIdentifier
token: m
element: <testLibrary>::@class::C::@method::m
element: MethodMember
baseElement: <testLibrary>::@class::C::@method::m
substitution: {T: T, S0: S0, S1: S1}
staticType: void Function<S0 extends T, S1 extends List<S0>>(S0, S1)
argumentList: ArgumentList
leftParenthesis: (
@@ -5025,13 +5027,13 @@ MethodInvocation
NullLiteral
literal: null
correspondingParameter: ParameterMember
baseElement: <testLibrary>::@class::C::@method::m::@formalParameter::p0
baseElement: p0@null
substitution: {S0: T, S1: List<T>}
staticType: Null
NullLiteral
literal: null
correspondingParameter: ParameterMember
baseElement: <testLibrary>::@class::C::@method::m::@formalParameter::p1
baseElement: p1@null
substitution: {S0: T, S1: List<T>}
staticType: Null
rightParenthesis: )
@@ -5062,7 +5064,9 @@ class C<T> {
MethodInvocation
methodName: SimpleIdentifier
token: m
element: <testLibrary>::@class::C::@method::m
element: MethodMember
baseElement: <testLibrary>::@class::C::@method::m
substitution: {T: T, S0: S0, S1: S1}
staticType: Map<S0, S1> Function<S0 extends T, S1 extends List<S0>>()
argumentList: ArgumentList
leftParenthesis: (
@@ -5094,7 +5098,9 @@ class C<T> {
MethodInvocation
methodName: SimpleIdentifier
token: m
element: <testLibrary>::@class::C::@method::m
element: MethodMember
baseElement: <testLibrary>::@class::C::@method::m
substitution: {T: T, S: S}
staticType: void Function<S extends T>(S)
argumentList: ArgumentList
leftParenthesis: (
@@ -5102,7 +5108,7 @@ MethodInvocation
NullLiteral
literal: null
correspondingParameter: ParameterMember
baseElement: <testLibrary>::@class::C::@method::m::@formalParameter::p0
baseElement: p0@null
substitution: {S: T}
staticType: Null
rightParenthesis: )
@@ -5132,7 +5138,9 @@ class C<T> {
MethodInvocation
methodName: SimpleIdentifier
token: m
element: <testLibrary>::@class::C::@method::m
element: MethodMember
baseElement: <testLibrary>::@class::C::@method::m
substitution: {T: T, S: S}
staticType: S Function<S extends T>()
argumentList: ArgumentList
leftParenthesis: (
@@ -1043,7 +1043,9 @@ Block
ExpressionStatement
expression: SimpleIdentifier
token: _t
element: <testLibrary>::@class::C::@getter::_t
element: GetterMember
baseElement: <testLibrary>::@class::C::@getter::_t
substitution: {T: T}
staticType: T
semicolon: ;
ExpressionStatement
@@ -1070,7 +1072,9 @@ Block
ExpressionStatement
expression: SimpleIdentifier
token: _t
element: <testLibrary>::@class::C::@getter::_t
element: GetterMember
baseElement: <testLibrary>::@class::C::@getter::_t
substitution: {T: T}
staticType: T?
semicolon: ;
ExpressionStatement
@@ -1224,7 +1228,9 @@ Block
expression: FunctionExpressionInvocation
function: SimpleIdentifier
token: _f
element: <testLibrary>::@class::C::@getter::_f
element: GetterMember
baseElement: <testLibrary>::@class::C::@getter::_f
substitution: {T: T}
staticType: T Function()
argumentList: ArgumentList
leftParenthesis: (
@@ -1265,7 +1271,9 @@ Block
expression: FunctionExpressionInvocation
function: SimpleIdentifier
token: _f
element: <testLibrary>::@class::C::@getter::_f
element: GetterMember
baseElement: <testLibrary>::@class::C::@getter::_f
substitution: {T: T}
staticType: T? Function()
argumentList: ArgumentList
leftParenthesis: (
@@ -1928,7 +1928,9 @@ class C<T extends MyFunction> {
FunctionExpressionInvocation
function: SimpleIdentifier
token: foo
element: <testLibrary>::@class::C::@getter::foo
element: GetterMember
baseElement: <testLibrary>::@class::C::@getter::foo
substitution: {T: T}
staticType: T
argumentList: ArgumentList
leftParenthesis: (
@@ -4217,7 +4219,9 @@ class C<T extends A> {
MethodInvocation
target: SimpleIdentifier
token: a
element: <testLibrary>::@class::C::@getter::a
element: GetterMember
baseElement: <testLibrary>::@class::C::@getter::a
substitution: {T: T}
staticType: T
operator: .
methodName: SimpleIdentifier
@@ -82,9 +82,10 @@ bool _isPostfixExpressionOperandEqualToVariable(
VariableElement variableElement,
) {
if (n is PostfixExpression) {
var operand = n.operand;
return operand is SimpleIdentifier &&
_isElementEqualToVariable(operand.element, variableElement);
return _isSimpleIdentifierElementEqualToVariable(
n.operand,
variableElement,
);
}
return false;
}
@@ -198,9 +199,11 @@ class _ValidUseVisitor extends RecursiveAstVisitor<void> {
@override
void visitAssignmentExpression(AssignmentExpression node) {
// Being assigned another reference.
if (node.rightHandSide is SimpleIdentifier) {
var rightHandSide = node.rightHandSide;
if (rightHandSide is SimpleIdentifier) {
var assignedElement = node.writeElement;
if (_isElementEqualToVariable(
node.writeElement,
assignedElement,
variable.declaredFragment?.element,
)) {
containsValidUse = true;
@@ -213,6 +216,15 @@ class _ValidUseVisitor extends RecursiveAstVisitor<void> {
containsValidUse = true;
return;
}
// Assigned element is instance member.
if (assignedElement == null &&
_isSimpleIdentifierElementEqualToVariable(
rightHandSide,
variableElement,
)) {
containsValidUse = true;
return;
}
}
super.visitAssignmentExpression(node);
}
@@ -310,8 +322,13 @@ class _ValidUseVisitor extends RecursiveAstVisitor<void> {
enum _VariableType { field, local }
extension on Element? {
bool matches(VariableElement? requested) => switch (this) {
PropertyAccessorElement(:var variable) => variable == requested,
_ => false,
};
bool matches(VariableElement? requested) {
var baseElement = this?.baseElement;
if (baseElement is PropertyAccessorElement) {
return baseElement.variable == requested;
} else if (baseElement is FieldElement) {
return baseElement == requested;
}
return false;
}
}
@@ -112,6 +112,21 @@ class B {
''');
}
test_privateField_canceled_withNullCheck() async {
await assertNoDiagnostics(r'''
import 'dart:async';
class C<T> {
StreamSubscription<T>? subscription;
void unsubscribe() {
if (subscription != null) {
subscription!.cancel();
subscription = null;
}
}
}
''');
}
test_privateField_notCanceled() async {
await assertDiagnostics(
r'''
+84 -84
View File
@@ -39,23 +39,6 @@ class ResolveNameInScopeTest extends PubPackageResolutionTest {
return result;
}
test_class_getter_different_fromExtends_thisClassSetter() async {
await assertNoDiagnostics('''
class A {
int get foo => 0;
}
class B extends A {
set foo(int _) {}
void bar() {
this.foo;
}
}
''');
_checkGetterDifferent(findElement.setter('foo'));
}
test_class_getter_different_importScope() async {
newFile('$testPackageLibPath/a.dart', r'''
set foo(int _) {}
@@ -123,6 +106,36 @@ class B extends A {
_checkGetterNone();
}
test_class_getter_none_fromThisClass() async {
await assertNoDiagnostics('''
class A {
int get foo => 0;
}
class B extends A {
set foo(int _) {}
void bar() {
this.foo;
}
}
''');
_checkGetterNone();
}
test_class_getter_none_thisClass() async {
await assertNoDiagnostics('''
class A {
int get foo => 0;
void bar() {
this.foo;
}
}
''');
_checkGetterNone();
}
test_class_getter_requested_importScope() async {
newFile('$testPackageLibPath/a.dart', r'''
int get foo => 0;
@@ -147,19 +160,6 @@ class B extends A {
_checkGetterRequested(import.topGet('foo'));
}
test_class_getter_requested_thisClass() async {
await assertNoDiagnostics('''
class A {
int get foo => 0;
void bar() {
this.foo;
}
}
''');
_checkGetterRequested(findElement.getter('foo'));
}
test_class_method_different_fromExtends_topSetter() async {
await assertNoDiagnostics('''
class A {
@@ -207,6 +207,19 @@ class A {
_checkMethodNone();
}
test_class_method_none_thisClass() async {
await assertNoDiagnostics('''
class A {
void foo() {}
void bar() {
this.foo();
}
}
''');
_checkMethodNone();
}
test_class_method_requested_formalParameter_constructor() async {
await assertNoDiagnostics('''
class A {
@@ -477,19 +490,6 @@ class A {
_checkMethodRequestedLocalVariable();
}
test_class_method_requested_thisClass() async {
await assertNoDiagnostics('''
class A {
void foo() {}
void bar() {
this.foo();
}
}
''');
_checkMethodRequested(findElement.method('foo'));
}
test_class_method_requested_typeParameter_method() async {
await assertNoDiagnostics('''
class A {
@@ -575,6 +575,34 @@ class B extends A {
_checkSetterNone();
}
test_class_setter_none_thisClass() async {
await assertNoDiagnostics('''
class A {
set foo(int _) {}
void bar() {
this.foo = 0;
}
}
''');
_checkSetterNone();
}
test_class_setter_none_thisClass_topLevelFunction() async {
await assertNoDiagnostics('''
class A {
set foo(int _) {}
void bar() {
this.foo = 0;
}
}
void foo() {}
''');
_checkSetterNone();
}
test_class_setter_requested_fromExtends_topLevelVariable() async {
await assertNoDiagnostics('''
class A {
@@ -616,34 +644,6 @@ class B extends A {
_checkSetterRequested(import.topSet('foo'));
}
test_class_setter_requested_thisClass() async {
await assertNoDiagnostics('''
class A {
set foo(int _) {}
void bar() {
this.foo = 0;
}
}
''');
_checkSetterRequested(findElement.setter('foo'));
}
test_class_setter_requested_thisClass_topLevelFunction() async {
await assertNoDiagnostics('''
class A {
set foo(int _) {}
void bar() {
this.foo = 0;
}
}
void foo() {}
''');
_checkSetterRequested(findElement.setter('foo'));
}
test_class_typeParameter_inConstructor() async {
await assertNoDiagnostics('''
class A<T> {
@@ -781,6 +781,19 @@ typedef A<T> = List<T>;
_resultRequested(node, 'T', false, findElement.typeParameter('T'));
}
test_mixin_method_none_thisClass() async {
await assertNoDiagnostics('''
mixin M {
void foo() {}
void bar() {
this.foo();
}
}
''');
_checkMethodNone();
}
test_mixin_method_requested_formalParameter_method() async {
await assertNoDiagnostics('''
mixin M {
@@ -794,19 +807,6 @@ mixin M {
_checkMethodRequested(findElement.parameter('foo'));
}
test_mixin_method_requested_thisClass() async {
await assertNoDiagnostics('''
mixin M {
void foo() {}
void bar() {
this.foo();
}
}
''');
_checkMethodRequested(findElement.method('foo'));
}
test_mixin_typeParameter_inField() async {
await assertNoDiagnostics('''
mixin A<T> {