[cfe] Handle extensions in Extension?.member check

Closes https://github.com/dart-lang/sdk/issues/49127

Change-Id: I26ce537dbed908789333f4aa6baa6b8487e5e993
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246447
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
Johnni Winther
2022-05-31 13:11:03 +00:00
committed by Commit Bot
parent 40ad7aee40
commit 69b9af2230
13 changed files with 170 additions and 12 deletions
@@ -3845,6 +3845,29 @@ const MessageCode messageExtensionDeclaresInstanceField = const MessageCode(
correctionMessage:
r"""Try removing the field declaration or making it a static field""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String name)>
templateExtensionInNullAwareReceiver =
const Template<Message Function(String name)>(
problemMessageTemplate: r"""The extension '#name' cannot be null.""",
correctionMessageTemplate: r"""Try replacing '?.' with '.'""",
withArguments: _withArgumentsExtensionInNullAwareReceiver);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(String name)> codeExtensionInNullAwareReceiver =
const Code<Message Function(String name)>("ExtensionInNullAwareReceiver",
severity: Severity.warning);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsExtensionInNullAwareReceiver(String name) {
if (name.isEmpty) throw 'No name provided';
name = demangleMixinApplicationName(name);
return new Message(codeExtensionInNullAwareReceiver,
problemMessage: """The extension '${name}' cannot be null.""",
correctionMessage: """Try replacing '?.' with '.'""",
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String name)>
templateExtensionMemberConflictsWithObjectMember =
@@ -2947,7 +2947,7 @@ class BodyBuilder extends StackListenerImpl
assert(declaration.isStatic || declaration.isTopLevel);
MemberBuilder memberBuilder = declaration as MemberBuilder;
return new StaticAccessGenerator(
this, token, name, memberBuilder.member, null);
this, token, name, memberBuilder.parent, memberBuilder.member, null);
} else if (declaration is PrefixBuilder) {
assert(prefix == null);
return new PrefixUseGenerator(this, token, declaration);
@@ -20,6 +20,7 @@ import '../builder/class_builder.dart';
import '../builder/declaration_builder.dart';
import '../builder/extension_builder.dart';
import '../builder/invalid_type_declaration_builder.dart';
import '../builder/library_builder.dart';
import '../builder/member_builder.dart';
import '../builder/named_type_builder.dart';
import '../builder/nullability_builder.dart';
@@ -1386,12 +1387,18 @@ class StaticAccessGenerator extends Generator {
final int? typeOffset;
final bool isNullAware;
/// The builder for the parent of [readTarget] and [writeTarget]. This is
/// either the builder for the enclosing library, class, or extension.
final Builder? parentBuilder;
StaticAccessGenerator(ExpressionGeneratorHelper helper, Token token,
this.targetName, this.readTarget, this.writeTarget,
this.targetName, this.parentBuilder, this.readTarget, this.writeTarget,
{this.typeOffset, this.isNullAware: false})
// ignore: unnecessary_null_comparison
: assert(targetName != null),
assert(readTarget != null || writeTarget != null),
assert(parentBuilder is DeclarationBuilder ||
parentBuilder is LibraryBuilder),
super(helper, token);
factory StaticAccessGenerator.fromBuilder(
@@ -1402,19 +1409,44 @@ class StaticAccessGenerator extends Generator {
MemberBuilder? setterBuilder,
{int? typeOffset,
bool isNullAware: false}) {
return new StaticAccessGenerator(helper, token, targetName,
getterBuilder?.readTarget, setterBuilder?.writeTarget,
typeOffset: typeOffset, isNullAware: isNullAware);
// If both [getterBuilder] and [setterBuilder] exist, they must both be
// either top level (potentially from different libraries) or from the same
// class/extension.
assert(getterBuilder == null ||
setterBuilder == null ||
(getterBuilder.parent is LibraryBuilder &&
setterBuilder.parent is LibraryBuilder) ||
getterBuilder.parent == setterBuilder.parent);
return new StaticAccessGenerator(
helper,
token,
targetName,
getterBuilder?.parent ?? setterBuilder?.parent,
getterBuilder?.readTarget,
setterBuilder?.writeTarget,
typeOffset: typeOffset,
isNullAware: isNullAware);
}
void _reportNonNullableInNullAwareWarningIfNeeded() {
if (isNullAware && _helper.libraryBuilder.isNonNullableByDefault) {
String className = (readTarget ?? writeTarget)!.enclosingClass!.name;
_helper.libraryBuilder.addProblem(
templateClassInNullAwareReceiver.withArguments(className),
typeOffset ?? fileOffset,
typeOffset != null ? className.length : noLength,
_helper.uri);
DeclarationBuilder declarationBuilder =
parentBuilder as DeclarationBuilder;
if (declarationBuilder.isExtension) {
String extensionName = declarationBuilder.name;
_helper.libraryBuilder.addProblem(
templateExtensionInNullAwareReceiver.withArguments(extensionName),
typeOffset ?? fileOffset,
typeOffset != null ? extensionName.length : noLength,
_helper.uri);
} else {
String className = declarationBuilder.name;
_helper.libraryBuilder.addProblem(
templateClassInNullAwareReceiver.withArguments(className),
typeOffset ?? fileOffset,
typeOffset != null ? className.length : noLength,
_helper.uri);
}
}
}
+1
View File
@@ -314,6 +314,7 @@ ExtendsVoid/example: Fail # Feature not yet enabled by default.
ExtensionDeclaresAbstractMember/example: Fail
ExtensionDeclaresConstructor/example: Fail
ExtensionDeclaresInstanceField/example: Fail
ExtensionInNullAwareReceiver/analyzerCode: Fail
ExtensionMemberConflictsWithObjectMember/analyzerCode: Fail
ExternalConstructorWithBody/part_wrapped_script1: Fail
ExternalConstructorWithBody/script1: Fail
+13
View File
@@ -5223,6 +5223,19 @@ ClassInNullAwareReceiver:
C?.field;
}
ExtensionInNullAwareReceiver:
problemMessage: "The extension '#name' cannot be null."
correctionMessage: "Try replacing '?.' with '.'"
severity: WARNING
configuration: nnbd-strong
script: |
extension E on int {
static var field;
}
method() {
E?.field;
}
NonNullableNotAssignedError:
problemMessage: "Non-nullable variable '#name' must be assigned before it can be used."
configuration: nnbd-strong
@@ -187,7 +187,8 @@ Future<void> main() async {
"StaticAccessGenerator(offset: 4, targetName: foo,"
" readTarget: $uri::myGetter,"
" writeTarget: $uri::mySetter)",
new StaticAccessGenerator(helper, token, 'foo', getter, setter));
new StaticAccessGenerator(
helper, token, 'foo', libraryBuilder, getter, setter));
check(
"LoadLibraryGenerator(offset: 4,"
" builder: Instance of 'LoadLibraryBuilder')",
@@ -0,0 +1,11 @@
// Copyright (c) 2022, 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.
extension E on int {
static String s = "Lily was here";
}
test() {
E?.s;
}
@@ -0,0 +1,5 @@
extension E on int {
static String s = "Lily was here";
}
test() {}
@@ -0,0 +1,5 @@
extension E on int {
static String s = "Lily was here";
}
test() {}
@@ -0,0 +1,19 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
// Try replacing '?.' with '.'
// E?.s;
// ^
//
import self as self;
import "dart:core" as core;
extension E on core::int {
static field s = self::E|s;
}
static field core::String E|s = "Lily was here";
static method test() → dynamic {
self::E|s;
}
@@ -0,0 +1,19 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
// Try replacing '?.' with '.'
// E?.s;
// ^
//
import self as self;
import "dart:core" as core;
extension E on core::int {
static field s = self::E|s;
}
static field core::String E|s = "Lily was here";
static method test() → dynamic {
self::E|s;
}
@@ -0,0 +1,10 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
extension E on core::int {
static field s = self::E|s;
}
static field core::String E|s;
static method test() → dynamic
;
@@ -0,0 +1,19 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
// Try replacing '?.' with '.'
// E?.s;
// ^
//
import self as self;
import "dart:core" as core;
extension E on core::int {
static field s = self::E|s;
}
static field core::String E|s = "Lily was here";
static method test() → dynamic {
self::E|s;
}