Version 2.12.0-249.0.dev
Merge commit '5569f10236f522e62adea480c1de4de244074359' into 'dev'
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml
new file mode 100644
index 0000000..1f4e0da3
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml
@@ -0,0 +1,56 @@
+# Copyright (c) 2021, 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.md file.
+
+type: newworld
+target: VM
+worlds:
+ - entry: main.dart
+ sources:
+ .dart_tool/package_config.json: |
+ {
+ "configVersion": 2,
+ "packages": [
+ {
+ "name": "flutter",
+ "rootUri": "../flutter",
+ "languageVersion": "2.12"
+ }
+ ]
+ }
+ main.dart: |
+ import 'package:flutter/object.dart';
+ import 'lib.dart';
+
+ class Adaptor extends RenderFoo with LibMixin {}
+
+ class AdaptorElement extends RenderObject {
+ Adaptor get renderObject => super.renderObject;
+ void foo() {
+ print(renderObject.constraints.axis);
+ }
+ }
+ lib.dart: |
+ import 'package:flutter/object.dart';
+ mixin LibMixin on RenderObject {}
+ flutter/object.dart: |
+ class RenderFoo extends RenderObject {
+ FooConstraints get constraints => super.constraints as FooConstraints;
+ }
+ class FooConstraints extends Constraints {
+ String get axis => "hello";
+ }
+ class Constraints {}
+ class RenderObject {
+ Constraints get constraints => new Constraints();
+ RenderObject get renderObject => this;
+ }
+ expectedLibraryCount: 3
+
+ - entry: main.dart
+ worldType: updated
+ errors: true
+ expectInitializeFromDill: false
+ invalidate:
+ - main.dart
+ expectedLibraryCount: 3
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml.world.1.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml.world.1.expect
new file mode 100644
index 0000000..03611f5
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml.world.1.expect
@@ -0,0 +1,100 @@
+main = <No Member>;
+library from "package:flutter/object.dart" as obj {
+
+ class RenderFoo extends obj::RenderObject {
+ synthetic constructor •() → obj::RenderFoo
+ : super obj::RenderObject::•()
+ ;
+ get constraints() → obj::FooConstraints
+ return super.{obj::RenderObject::constraints} as{ForNonNullableByDefault} obj::FooConstraints;
+ }
+ class FooConstraints extends obj::Constraints {
+ synthetic constructor •() → obj::FooConstraints
+ : super obj::Constraints::•()
+ ;
+ get axis() → dart.core::String
+ return "hello";
+ }
+ class Constraints extends dart.core::Object {
+ synthetic constructor •() → obj::Constraints
+ : super dart.core::Object::•()
+ ;
+ }
+ class RenderObject extends dart.core::Object {
+ synthetic constructor •() → obj::RenderObject
+ : super dart.core::Object::•()
+ ;
+ get constraints() → obj::Constraints
+ return new obj::Constraints::•();
+ get renderObject() → obj::RenderObject
+ return this;
+ }
+}
+library from "org-dartlang-test:///lib.dart" as lib {
+
+ import "package:flutter/object.dart";
+
+ abstract class LibMixin extends obj::RenderObject /*isMixinDeclaration*/ {
+ abstract member-signature get constraints() → obj::Constraints*; -> obj::RenderObject::constraints
+ abstract member-signature get renderObject() → obj::RenderObject*; -> obj::RenderObject::renderObject
+ abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+ abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+ abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+ abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+ abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+ abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+ abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+ abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+ abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+ abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+ }
+}
+library from "org-dartlang-test:///main.dart" as main {
+
+ import "package:flutter/object.dart";
+ import "org-dartlang-test:///lib.dart";
+
+ abstract class _Adaptor&RenderFoo&LibMixin extends obj::RenderFoo implements lib::LibMixin /*isAnonymousMixin,isEliminatedMixin*/ {
+ synthetic constructor •() → main::_Adaptor&RenderFoo&LibMixin*
+ : super obj::RenderFoo::•()
+ ;
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ constraints() → obj::Constraints*; -> obj::RenderObject::constraints
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ renderObject() → obj::RenderObject*; -> obj::RenderObject::renderObject
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+ abstract member-signature operator /* from org-dartlang-test:///lib.dart */ ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ toString() → dart.core::String*; -> dart.core::Object::toString
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+ }
+ class Adaptor extends main::_Adaptor&RenderFoo&LibMixin {
+ synthetic constructor •() → main::Adaptor*
+ : super main::_Adaptor&RenderFoo&LibMixin::•()
+ ;
+ }
+ class AdaptorElement extends obj::RenderObject {
+ synthetic constructor •() → main::AdaptorElement*
+ : super obj::RenderObject::•()
+ ;
+ get renderObject() → main::Adaptor*
+ return super.{obj::RenderObject::renderObject} as{TypeError} main::Adaptor*;
+ method foo() → void {
+ dart.core::print(this.{main::AdaptorElement::renderObject}.{main::_Adaptor&RenderFoo&LibMixin::constraints}.{obj::FooConstraints::axis});
+ }
+ abstract member-signature get constraints() → obj::Constraints*; -> obj::RenderObject::constraints
+ abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+ abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+ abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+ abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+ abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+ abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+ abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+ abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+ abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+ abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+ }
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml.world.2.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml.world.2.expect
new file mode 100644
index 0000000..e251ab0
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/error_on_recompile_with_no_change.yaml.world.2.expect
@@ -0,0 +1,109 @@
+main = <No Member>;
+library from "package:flutter/object.dart" as obj {
+
+ class RenderFoo extends obj::RenderObject {
+ synthetic constructor •() → obj::RenderFoo
+ : super obj::RenderObject::•()
+ ;
+ get constraints() → obj::FooConstraints
+ return super.{obj::RenderObject::constraints} as{ForNonNullableByDefault} obj::FooConstraints;
+ }
+ class FooConstraints extends obj::Constraints {
+ synthetic constructor •() → obj::FooConstraints
+ : super obj::Constraints::•()
+ ;
+ get axis() → dart.core::String
+ return "hello";
+ }
+ class Constraints extends dart.core::Object {
+ synthetic constructor •() → obj::Constraints
+ : super dart.core::Object::•()
+ ;
+ }
+ class RenderObject extends dart.core::Object {
+ synthetic constructor •() → obj::RenderObject
+ : super dart.core::Object::•()
+ ;
+ get constraints() → obj::Constraints
+ return new obj::Constraints::•();
+ get renderObject() → obj::RenderObject
+ return this;
+ }
+}
+library from "org-dartlang-test:///lib.dart" as lib {
+
+ import "package:flutter/object.dart";
+
+ abstract class LibMixin extends obj::RenderObject /*isMixinDeclaration*/ {
+ abstract member-signature get constraints() → obj::Constraints*; -> obj::RenderObject::constraints
+ abstract member-signature get renderObject() → obj::RenderObject*; -> obj::RenderObject::renderObject
+ abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+ abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+ abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+ abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+ abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+ abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+ abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+ abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+ abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+ abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+ }
+}
+library from "org-dartlang-test:///main.dart" as main {
+//
+// Problems in library:
+//
+// org-dartlang-test:///main.dart:9:36: Error: The getter 'axis' isn't defined for the class 'Constraints'.
+// - 'Constraints' is from 'package:flutter/object.dart' ('org-dartlang-test:///flutter/object.dart').
+// Try correcting the name to the name of an existing getter, or defining a getter or field named 'axis'.
+// print(renderObject.constraints.axis);
+// ^^^^
+//
+
+ import "package:flutter/object.dart";
+ import "org-dartlang-test:///lib.dart";
+
+ abstract class _Adaptor&RenderFoo&LibMixin extends obj::RenderFoo implements lib::LibMixin /*isAnonymousMixin,isEliminatedMixin*/ {
+ synthetic constructor •() → main::_Adaptor&RenderFoo&LibMixin*
+ : super obj::RenderFoo::•()
+ ;
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ constraints() → obj::Constraints*; -> obj::RenderObject::constraints
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ renderObject() → obj::RenderObject*; -> obj::RenderObject::renderObject
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+ abstract member-signature operator /* from org-dartlang-test:///lib.dart */ ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ toString() → dart.core::String*; -> dart.core::Object::toString
+ abstract member-signature method /* from org-dartlang-test:///lib.dart */ noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+ abstract member-signature get /* from org-dartlang-test:///lib.dart */ runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+ }
+ class Adaptor extends main::_Adaptor&RenderFoo&LibMixin {
+ synthetic constructor •() → main::Adaptor*
+ : super main::_Adaptor&RenderFoo&LibMixin::•()
+ ;
+ }
+ class AdaptorElement extends obj::RenderObject {
+ synthetic constructor •() → main::AdaptorElement*
+ : super obj::RenderObject::•()
+ ;
+ get renderObject() → main::Adaptor*
+ return super.{obj::RenderObject::renderObject} as{TypeError} main::Adaptor*;
+ method foo() → void {
+ dart.core::print(invalid-expression "org-dartlang-test:///main.dart:9:36: Error: The getter 'axis' isn't defined for the class 'Constraints'.\n - 'Constraints' is from 'package:flutter/object.dart' ('org-dartlang-test:///flutter/object.dart').\nTry correcting the name to the name of an existing getter, or defining a getter or field named 'axis'.\n print(renderObject.constraints.axis);\n ^^^^");
+ }
+ abstract member-signature get constraints() → obj::Constraints*; -> obj::RenderObject::constraints
+ abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+ abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+ abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+ abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+ abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+ abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+ abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+ abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+ abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+ abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+ }
+}
diff --git a/pkg/nnbd_migration/lib/instrumentation.dart b/pkg/nnbd_migration/lib/instrumentation.dart
index 5295242..a5fa5ce 100644
--- a/pkg/nnbd_migration/lib/instrumentation.dart
+++ b/pkg/nnbd_migration/lib/instrumentation.dart
@@ -274,6 +274,7 @@
implicitMixinSuperCall,
implicitNullInitializer,
implicitNullReturn,
+ implicitThis,
inferredTypeParameterInstantiation,
instanceCreation,
instantiateToBounds,
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 2213ef3..5f30b51 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -10,6 +10,7 @@
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_system.dart';
+import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart' show TypeSystemImpl;
@@ -1268,6 +1269,7 @@
}
} else if (target == null && callee.enclosingElement is ClassElement) {
targetType = _thisOrSuper(node);
+ _checkThisNotNull(targetType, node);
}
DecoratedType expressionType;
DecoratedType calleeType;
@@ -1575,6 +1577,7 @@
@override
DecoratedType visitSimpleIdentifier(SimpleIdentifier node) {
+ DecoratedType targetType;
DecoratedType result;
var staticElement = getWriteOrReadElement(node);
if (staticElement is PromotableElement) {
@@ -1594,15 +1597,16 @@
} else if (staticElement is FunctionElement ||
staticElement is MethodElement ||
staticElement is ConstructorElement) {
- result = getOrComputeElementType(staticElement,
- targetType: staticElement.enclosingElement is ClassElement
- ? _thisOrSuper(node)
- : null);
+ if (staticElement.enclosingElement is ClassElement) {
+ targetType = _thisOrSuper(node);
+ }
+ result = getOrComputeElementType(staticElement, targetType: targetType);
} else if (staticElement is PropertyAccessorElement) {
- var elementType = getOrComputeElementType(staticElement,
- targetType: staticElement.enclosingElement is ClassElement
- ? _thisOrSuper(node)
- : null);
+ if (staticElement.enclosingElement is ClassElement) {
+ targetType = _thisOrSuper(node);
+ }
+ var elementType =
+ getOrComputeElementType(staticElement, targetType: targetType);
result = staticElement.isGetter
? elementType.returnType
: elementType.positionalParameters[0];
@@ -1621,6 +1625,9 @@
_unimplemented(node,
'Simple identifier with a static element of type ${staticElement.runtimeType}');
}
+ if (targetType != null) {
+ _checkThisNotNull(targetType, node);
+ }
return result;
}
@@ -1986,6 +1993,18 @@
return sourceType;
}
+ /// Generates the appropriate edge to assert that the value of `this` is
+ /// non-null.
+ void _checkThisNotNull(DecoratedType thisType, AstNode node) {
+ // `this` can only be `null` in extensions, so if we're not in an extension,
+ // there's nothing to do.
+ if (_currentExtendedType == null) return;
+ var origin = ImplicitThisOrigin(source, node);
+ var hard =
+ _postDominatedLocals.isInScope(_postDominatedLocals.extensionThis);
+ _graph.makeNonNullable(thisType.node, origin, hard: hard, guards: _guards);
+ }
+
@override
void _connect(NullabilityNode source, NullabilityNode destination,
EdgeOrigin origin, FixReasonTarget edgeTarget,
@@ -2397,9 +2416,10 @@
}
}
if (destinationExpression != null) {
- var element = _postDominatedLocals
- .removeReferenceFromAllScopes(destinationExpression);
+ var element =
+ _postDominatedLocals.referencedElement(destinationExpression);
if (element != null) {
+ _postDominatedLocals.removeFromAllScopes(element);
_elementsWrittenToInLocalFunction?.add(element);
}
}
@@ -2451,6 +2471,9 @@
_addParametersToFlowAnalysis(parameters);
// Push a scope of post-dominated declarations on the stack.
_postDominatedLocals.pushScope(elements: declaredElement.parameters);
+ if (declaredElement.enclosingElement is ExtensionElement) {
+ _postDominatedLocals.add(_postDominatedLocals.extensionThis);
+ }
try {
_dispatchList(initializers);
if (declaredElement is ConstructorElement &&
@@ -3658,23 +3681,23 @@
///
/// Contains helpers for dealing with expressions as if they were elements.
class _ScopedLocalSet extends ScopedSet<Element> {
+ /// The synthetic element we use as a stand-in for `this` when analyzing
+ /// extension methods.
+ Element get extensionThis => DynamicElementImpl.instance;
+
bool isReferenceInScope(Expression expression) {
- expression = expression.unParenthesized;
- if (expression is SimpleIdentifier) {
- var element = expression.staticElement;
- return isInScope(element);
- }
- return false;
+ var element = referencedElement(expression);
+ return element != null && isInScope(element);
}
- /// If [expression] references an element, removes that element from all
- /// scopes and returns it. Otherwise returns `null`.
- Element removeReferenceFromAllScopes(Expression expression) {
+ /// Returns the element referenced directly by [expression], if any; otherwise
+ /// returns `null`.
+ Element referencedElement(Expression expression) {
expression = expression.unParenthesized;
if (expression is SimpleIdentifier) {
- var element = expression.staticElement;
- removeFromAllScopes(element);
- return element;
+ return expression.staticElement;
+ } else if (expression is ThisExpression || expression is SuperExpression) {
+ return extensionThis;
} else {
return null;
}
diff --git a/pkg/nnbd_migration/lib/src/edge_origin.dart b/pkg/nnbd_migration/lib/src/edge_origin.dart
index 5b42111..cff0b7b 100644
--- a/pkg/nnbd_migration/lib/src/edge_origin.dart
+++ b/pkg/nnbd_migration/lib/src/edge_origin.dart
@@ -85,6 +85,18 @@
EdgeOriginKind get kind => EdgeOriginKind.argumentErrorCheckNotNull;
}
+/// An edge origin used for edges that originated because of a tear-off of
+/// `call` on a function type.
+class CallTearOffOrigin extends EdgeOrigin {
+ CallTearOffOrigin(Source source, AstNode node) : super(source, node);
+
+ @override
+ String get description => 'tear-off of .call';
+
+ @override
+ EdgeOriginKind get kind => EdgeOriginKind.callTearOff;
+}
+
/// Edge origin resulting from the use of a value on the LHS of a compound
/// assignment.
class CompoundAssignmentOrigin extends EdgeOrigin {
@@ -312,6 +324,18 @@
ReturnStatement get node => super.node as ReturnStatement;
}
+/// Edge origin used for edges that arise from an implicit use of `this`, e.g.
+/// during a method call from an extension.
+class ImplicitThisOrigin extends EdgeOrigin {
+ ImplicitThisOrigin(Source source, AstNode node) : super(source, node);
+
+ @override
+ String get description => 'implicit use of `this`';
+
+ @override
+ EdgeOriginKind get kind => EdgeOriginKind.implicitThis;
+}
+
/// Edge origin resulting from the inference of a type parameter, which
/// can affects the nullability of that type parameter's bound.
class InferredTypeParameterInstantiationOrigin extends EdgeOrigin {
@@ -397,18 +421,6 @@
EdgeOriginKind get kind => EdgeOriginKind.listLengthConstructor;
}
-/// An edge origin used for edges that originated because of a tear-off of
-/// `call` on a function type.
-class CallTearOffOrigin extends EdgeOrigin {
- CallTearOffOrigin(Source source, AstNode node) : super(source, node);
-
- @override
- String get description => 'tear-off of .call';
-
- @override
- EdgeOriginKind get kind => EdgeOriginKind.callTearOff;
-}
-
/// An edge origin used for edges that originated because a literal expression
/// has a known nullability.
class LiteralOrigin extends EdgeOrigin {
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index 1d4748f..687b07b 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -495,6 +495,20 @@
(_shouldStayNullAware[node] ??= _fixBuilder._shouldStayNullAware(node));
}
+ /// Indicates whether the given [element] is a member of an extension on a
+ /// potentially nullable type (and hence the extension member can be invoked
+ /// on a nullable type without introducing a null check).
+ bool isNullableExtensionMember(Element element) {
+ if (element != null) {
+ var enclosingElement = element.enclosingElement;
+ if (enclosingElement is ExtensionElement) {
+ return _fixBuilder._typeSystem
+ .isPotentiallyNullable(enclosingElement.extendedType);
+ }
+ }
+ return false;
+ }
+
@override
bool isPropertyAccessNullAware(PropertyAccess node) {
return node.isNullAware &&
@@ -705,38 +719,49 @@
operatorType == TokenType.BANG_EQ) {
return false;
} else {
- return true;
+ return !isNullableExtensionMember(parent.staticElement);
}
}
} else if (parent is PrefixedIdentifier) {
- if (isDeclaredOnObject(parent.identifier.name)) {
+ if (isDeclaredOnObject(parent.identifier.name) ||
+ isNullableExtensionMember(parent.identifier.staticElement)) {
return false;
}
return identical(node, parent.prefix);
} else if (parent is PropertyAccess) {
- if (isDeclaredOnObject(parent.propertyName.name)) {
+ if (isDeclaredOnObject(parent.propertyName.name) ||
+ isNullableExtensionMember(parent.propertyName.staticElement)) {
return false;
}
// TODO(paulberry): what about cascaded?
return parent.operator.type == TokenType.PERIOD &&
identical(node, parent.target);
} else if (parent is MethodInvocation) {
- if (isDeclaredOnObject(parent.methodName.name)) {
+ if (isDeclaredOnObject(parent.methodName.name) ||
+ isNullableExtensionMember(parent.methodName.staticElement)) {
return false;
}
// TODO(paulberry): what about cascaded?
return parent.operator.type == TokenType.PERIOD &&
identical(node, parent.target);
} else if (parent is IndexExpression) {
- return identical(node, parent.target);
+ if (identical(node, parent.target)) {
+ return !isNullableExtensionMember(parent.staticElement);
+ } else {
+ return false;
+ }
} else if (parent is ConditionalExpression) {
return identical(node, parent.condition);
} else if (parent is FunctionExpressionInvocation) {
- return identical(node, parent.function);
+ if (identical(node, parent.function)) {
+ return !isNullableExtensionMember(parent.staticElement);
+ } else {
+ return false;
+ }
} else if (parent is PrefixExpression) {
// TODO(paulberry): for prefix increment/decrement, inserting a null check
// isn't sufficient.
- return true;
+ return !isNullableExtensionMember(parent.staticElement);
} else if (parent is ThrowExpression) {
return true;
}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 2ff78bf..7089d06 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -2116,6 +2116,163 @@
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
+ Future<void> test_extension_extended_type_nullability_intent() async {
+ var content = '''
+extension E on C {
+ String foo() => this.bar();
+}
+
+class C {
+ String bar() => null;
+}
+
+void test(C c, bool b) {
+ if (b) {
+ c.foo();
+ }
+}
+
+main() {
+ test(null, false);
+}
+''';
+ // The call to `bar` from `foo` should be taken as a demonstration that the
+ // extension E is not intended to apply to nullable types, so the call to
+ // `foo` should be null checked.
+ var expected = '''
+extension E on C {
+ String? foo() => this.bar();
+}
+
+class C {
+ String? bar() => null;
+}
+
+void test(C? c, bool b) {
+ if (b) {
+ c!.foo();
+ }
+}
+
+main() {
+ test(null, false);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_extension_null_check_non_nullable() async {
+ var content = '''
+class C {}
+extension E on C/*!*/ {
+ void m() {}
+}
+void f(C c, bool b) {
+ if (b) {
+ c.m();
+ }
+}
+void g() => f(null, false);
+''';
+ var expected = '''
+class C {}
+extension E on C {
+ void m() {}
+}
+void f(C? c, bool b) {
+ if (b) {
+ c!.m();
+ }
+}
+void g() => f(null, false);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_extension_null_check_non_nullable_generic() async {
+ var content = '''
+class C {}
+extension E<T extends Object/*!*/> on T/*!*/ {
+ void m() {}
+}
+void f(C c, bool b) {
+ if (b) {
+ c.m();
+ }
+}
+void g() => f(null, false);
+''';
+ var expected = '''
+class C {}
+extension E<T extends Object> on T {
+ void m() {}
+}
+void f(C? c, bool b) {
+ if (b) {
+ c!.m();
+ }
+}
+void g() => f(null, false);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_extension_null_check_nullable() async {
+ var content = '''
+class C {}
+extension E on C/*?*/ {
+ void m() {}
+}
+void f(C c, bool b) {
+ if (b) {
+ c.m();
+ }
+}
+void g() => f(null, false);
+''';
+ var expected = '''
+class C {}
+extension E on C? {
+ void m() {}
+}
+void f(C? c, bool b) {
+ if (b) {
+ c.m();
+ }
+}
+void g() => f(null, false);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_extension_null_check_nullable_generic() async {
+ var content = '''
+class C {}
+extension E<T extends Object/*?*/> on T/*!*/ {
+ void m() {}
+}
+void f(C c, bool b) {
+ if (b) {
+ c.m();
+ }
+}
+void g() => f(null, false);
+''';
+ var expected = '''
+class C {}
+extension E<T extends Object?> on T {
+ void m() {}
+}
+void f(C? c, bool b) {
+ if (b) {
+ c.m();
+ }
+}
+void g() => f(null, false);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_extension_null_check_target() async {
var content = '''
extension E on int/*!*/ {
@@ -2218,7 +2375,6 @@
await _checkSingleFileChanges(content, expected);
}
- @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40023')
Future<void> test_extension_nullableOnType_viaImplicitInvocation() async {
var content = '''
class C {}
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index e943de9..9558d4e 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -2952,6 +2952,87 @@
// adding assertion(s).
}
+ Future<void> test_extension_this_non_null_intent_explicit_direct() async {
+ await analyze('''
+extension on int {
+ f() => g(this);
+}
+void g(int i) {}
+''');
+ assertEdge(decoratedTypeAnnotation('int {').node,
+ decoratedTypeAnnotation('int i').node,
+ hard: true);
+ }
+
+ Future<void> test_extension_this_non_null_intent_explicit_method() async {
+ await analyze('''
+extension on int {
+ f() => this.abs();
+}
+''');
+ assertEdge(decoratedTypeAnnotation('int').node, never, hard: true);
+ }
+
+ Future<void>
+ test_extension_this_non_null_intent_explicit_property_get() async {
+ await analyze('''
+extension on int {
+ f() => this.isEven;
+}
+''');
+ assertEdge(decoratedTypeAnnotation('int').node, never, hard: true);
+ }
+
+ Future<void>
+ test_extension_this_non_null_intent_explicit_property_set() async {
+ await analyze('''
+class C {
+ int x;
+}
+extension on C /*reference*/ {
+ f() {
+ this.x = 0;
+ }
+}
+''');
+ assertEdge(decoratedTypeAnnotation('C /*reference*/').node, never,
+ hard: true);
+ }
+
+ Future<void> test_extension_this_non_null_intent_implicit_method() async {
+ await analyze('''
+extension on int {
+ f() => abs();
+}
+''');
+ assertEdge(decoratedTypeAnnotation('int').node, never, hard: true);
+ }
+
+ Future<void> test_extension_this_non_null_intent_implicit_property() async {
+ await analyze('''
+extension on int {
+ f() => isEven;
+}
+''');
+ assertEdge(decoratedTypeAnnotation('int').node, never, hard: true);
+ }
+
+ Future<void>
+ test_extension_this_non_null_intent_implicit_property_set() async {
+ await analyze('''
+class C {
+ int x;
+}
+extension on C /*reference*/ {
+ f() {
+ x = 0;
+ }
+}
+''');
+ assertEdge(decoratedTypeAnnotation('C /*reference*/').node, never,
+ hard: true);
+ }
+
Future<void> test_field_final_does_not_override_setter() async {
await analyze('''
abstract class A {
@@ -7712,11 +7793,11 @@
}
''');
expect(
- assertEdge(anyNode, decoratedTypeAnnotation('int f1').node, hard: false)
+ assertEdge(anyNode, decoratedTypeAnnotation('int f1').node, hard: true)
.sourceNode,
isNot(never));
expect(
- assertEdge(anyNode, decoratedTypeAnnotation('int f2').node, hard: false)
+ assertEdge(anyNode, decoratedTypeAnnotation('int f2').node, hard: true)
.sourceNode,
never);
expect(hasNullCheckHint(findNode.this_('this/*!*/')), isTrue);
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index 7989905..3d11cdf 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -946,6 +946,32 @@
visitSubexpression(findNode.binary('=='), 'bool');
}
+ Future<void> test_binaryExpression_extensionMember_allowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*?*/ {
+ void operator+(C/*!*/ other) {}
+}
+f(C/*?*/ c) => c + c;
+''');
+ var binaryExpression = findNode.binary('c + c');
+ visitSubexpression(binaryExpression, 'void',
+ changes: {binaryExpression.rightOperand: isNullCheck});
+ }
+
+ Future<void> test_binaryExpression_extensionMember_disallowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*!*/ {
+ void operator+(C/*!*/ other) {}
+}
+f(C/*?*/ c) => c + c;
+''');
+ var binaryExpression = findNode.binary('c + c');
+ visitSubexpression(binaryExpression, 'void',
+ changes: {binaryExpression.leftOperand: isNullCheck});
+ }
+
Future<void> test_binaryExpression_question_question() async {
await analyze('''
_f(int/*?*/ x, double/*?*/ y) {
@@ -1390,6 +1416,35 @@
visitSubexpression(findNode.functionExpressionInvocation('d('), 'dynamic');
}
+ Future<void>
+ test_functionExpressionInvocation_extensionMember_allowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*?*/ {
+ void call() {}
+}
+f(C/*?*/ c) => c();
+''');
+ var functoinExpressionInvocation =
+ findNode.functionExpressionInvocation('c()');
+ visitSubexpression(functoinExpressionInvocation, 'void');
+ }
+
+ Future<void>
+ test_functionExpressionInvocation_extensionMember_disallowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*!*/ {
+ void call() {}
+}
+f(C/*?*/ c) => c();
+''');
+ var functionExpressionInvocation =
+ findNode.functionExpressionInvocation('c()');
+ visitSubexpression(functionExpressionInvocation, 'void',
+ changes: {functionExpressionInvocation.function: isNullCheck});
+ }
+
Future<void> test_functionExpressionInvocation_function_checked() async {
await analyze('''
_f(Function/*?*/ func) => func();
@@ -1660,6 +1715,31 @@
visitSubexpression(findNode.index('d[i]'), 'dynamic');
}
+ Future<void> test_indexExpression_extensionMember_allowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*?*/ {
+ int operator[](int index) => 0;
+}
+f(C/*?*/ c) => c[0];
+''');
+ var indexExpression = findNode.index('c[0]');
+ visitSubexpression(indexExpression, 'int');
+ }
+
+ Future<void> test_indexExpression_extensionMember_disallowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*!*/ {
+ int operator[](int index) => 0;
+}
+f(C/*?*/ c) => c[0];
+''');
+ var indexExpression = findNode.index('c[0]');
+ visitSubexpression(indexExpression, 'int',
+ changes: {indexExpression.target: isNullCheck});
+ }
+
Future<void> test_indexExpression_simple() async {
await analyze('''
class _C {
@@ -1931,6 +2011,40 @@
visitSubexpression(findNode.methodInvocation('d.f'), 'dynamic');
}
+ Future<void> test_methodInvocation_extensionMember_allowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*?*/ {
+ void foo() {}
+}
+f(C/*?*/ c) => c.foo();
+''');
+ var methodInvocation = findNode.methodInvocation('c.foo');
+ visitSubexpression(methodInvocation, 'void');
+ }
+
+ Future<void> test_methodInvocation_extensionMember_disallowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*!*/ {
+ void foo() {}
+}
+f(C/*?*/ c) => c.foo();
+''');
+ var methodInvocation = findNode.methodInvocation('c.foo');
+ visitSubexpression(methodInvocation, 'void',
+ changes: {methodInvocation.target: isNullCheck});
+ }
+
+ Future<void> test_methodInvocation_function_call_nullCheck() async {
+ await analyze('''
+f(void Function()/*?*/ x) => x.call();
+''');
+ var methodInvocation = findNode.methodInvocation('x.call');
+ visitSubexpression(methodInvocation, 'void',
+ changes: {methodInvocation.target: isNullCheck});
+ }
+
Future<void> test_methodInvocation_namedParameter() async {
await analyze('''
abstract class _C {
@@ -2474,6 +2588,31 @@
visitSubexpression(findNode.prefixed('d.x'), 'dynamic');
}
+ Future<void> test_prefixedIdentifier_extensionMember_allowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*?*/ {
+ int get foo => 0;
+}
+f(C/*?*/ c) => c.foo;
+''');
+ var prefixedIdentifier = findNode.prefixed('c.foo');
+ visitSubexpression(prefixedIdentifier, 'int');
+ }
+
+ Future<void> test_prefixedIdentifier_extensionMember_disallowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*!*/ {
+ int get foo => 0;
+}
+f(C/*?*/ c) => c.foo;
+''');
+ var prefixedIdentifier = findNode.prefixed('c.foo');
+ visitSubexpression(prefixedIdentifier, 'int',
+ changes: {prefixedIdentifier.prefix: isNullCheck});
+ }
+
Future<void> test_prefixedIdentifier_field_nonNullable() async {
await analyze('''
class _C {
@@ -2643,6 +2782,31 @@
changes: {findNode.simple('c);'): isNullCheck});
}
+ Future<void> test_prefixExpression_extensionMember_allowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*?*/ {
+ C operator-() => C();
+}
+f(C/*?*/ c) => -c;
+''');
+ var prefixExpression = findNode.prefix('-c');
+ visitSubexpression(prefixExpression, 'C');
+ }
+
+ Future<void> test_prefixExpression_extensionMember_disallowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*!*/ {
+ C operator-() => C();
+}
+f(C/*?*/ c) => -c;
+''');
+ var prefixExpression = findNode.prefix('-c');
+ visitSubexpression(prefixExpression, 'C',
+ changes: {prefixExpression.operand: isNullCheck});
+ }
+
Future<void> test_prefixExpression_increment_undoes_promotion() async {
await analyze('''
abstract class _C {
@@ -2768,6 +2932,31 @@
visitSubexpression(findNode.propertyAccess('(d).x'), 'dynamic');
}
+ Future<void> test_propertyAccess_extensionMember_allowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*?*/ {
+ int get foo => 0;
+}
+f(C/*?*/ Function() g) => g().foo;
+''');
+ var propertyAccess = findNode.propertyAccess('g().foo');
+ visitSubexpression(propertyAccess, 'int');
+ }
+
+ Future<void> test_propertyAccess_extensionMember_disallowsNull() async {
+ await analyze('''
+class C {}
+extension E on C/*!*/ {
+ int get foo => 0;
+}
+f(C/*?*/ Function() g) => g().foo;
+''');
+ var propertyAccess = findNode.propertyAccess('g().foo');
+ visitSubexpression(propertyAccess, 'int',
+ changes: {propertyAccess.target: isNullCheck});
+ }
+
Future<void> test_propertyAccess_field_nonNullable() async {
await analyze('''
class _C {
diff --git a/tools/VERSION b/tools/VERSION
index 6e464de..fc35def 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 248
+PRERELEASE 249
PRERELEASE_PATCH 0
\ No newline at end of file