Skip to content

Commit

Permalink
support @virtual fields, fix #27384
Browse files Browse the repository at this point in the history
  • Loading branch information
John Messerly committed Sep 19, 2016
1 parent 88a64b7 commit 865e808
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 19 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@
}
```
* New feature - use `@virtual` to allow field overrides in strong mode
(SDK issue [27384](https://github.com/dart-lang/sdk/issues/27384)).
```dart
import 'package:meta/meta.dart' show virtual;
class Base {
@virtual int x;
}
class Derived extends Base {
int x;
// Expose the hidden storage slot:
int get superX => super.x;
set superX(int v) { super.x = v; }
}
```
* Breaking change - infer list and map literals from the context type as well as
their values, consistent with generic methods and instance creation
(SDK issue [27151](https://github.com/dart-lang/sdk/issues/27151)).
Expand Down
5 changes: 5 additions & 0 deletions pkg/analyzer/lib/dart/element/element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,11 @@ abstract class FieldElement
*/
bool get isEnumConstant;

/**
* Returns `true` if this field can be overridden in strong mode.
*/
bool get isVirtual;

@override
AstNode computeNode();
}
Expand Down
28 changes: 28 additions & 0 deletions pkg/analyzer/lib/src/dart/element/element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2513,6 +2513,12 @@ class ElementAnnotationImpl implements ElementAnnotation {
*/
static String _REQUIRED_VARIABLE_NAME = "required";

/**
* The name of the top-level variable used to mark a member as intended to be
* overridden.
*/
static String _VIRTUAL_VARIABLE_NAME = "virtual";

/**
* The element representing the field, variable, or constructor being used as
* an annotation.
Expand Down Expand Up @@ -2615,6 +2621,18 @@ class ElementAnnotationImpl implements ElementAnnotation {
element.name == _REQUIRED_VARIABLE_NAME &&
element.library?.name == _META_LIB_NAME;

/**
* Return `true` if this annotation marks the associated member as supporting
* overrides.
*
* This is currently used by fields in Strong Mode, as other members are
* already virtual-by-default.
*/
bool get isVirtual =>
element is PropertyAccessorElement &&
element.name == _VIRTUAL_VARIABLE_NAME &&
element.library?.name == _META_LIB_NAME;

/**
* Get the library containing this annotation.
*/
Expand Down Expand Up @@ -4186,6 +4204,16 @@ class FieldElementImpl extends PropertyInducingElementImpl
return hasModifier(Modifier.STATIC);
}

@override
bool get isVirtual {
for (ElementAnnotationImpl annotation in metadata) {
if (annotation.isVirtual) {
return true;
}
}
return false;
}

@override
ElementKind get kind => ElementKind.FIELD;

Expand Down
3 changes: 3 additions & 0 deletions pkg/analyzer/lib/src/dart/element/handle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,9 @@ class FieldElementHandle extends PropertyInducingElementHandle
@override
bool get isEnumConstant => actualElement.isEnumConstant;

@override
bool get isVirtual => actualElement.isVirtual;

@override
ElementKind get kind => ElementKind.FIELD;

Expand Down
3 changes: 3 additions & 0 deletions pkg/analyzer/lib/src/dart/element/member.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ class FieldMember extends VariableMember implements FieldElement {
@override
bool get isEnumConstant => baseElement.isEnumConstant;

@override
bool get isVirtual => baseElement.isVirtual;

@override
DartType get propagatedType => substituteFor(baseElement.propagatedType);

Expand Down
3 changes: 3 additions & 0 deletions pkg/analyzer/lib/src/summary/link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4001,6 +4001,9 @@ class ParameterElementForLink_VariableSetter implements ParameterElementImpl {

ParameterElementForLink_VariableSetter(this.enclosingElement);

@override
bool inheritsCovariant = false;

@override
bool get isCovariant => false;

Expand Down
12 changes: 7 additions & 5 deletions pkg/analyzer/lib/src/task/strong/checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/error/codes.dart' show StrongModeCode;
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
Expand Down Expand Up @@ -74,10 +75,10 @@ Element _getKnownElement(Expression expression) {

/// Return the field on type corresponding to member, or null if none
/// exists or the "field" is actually a getter/setter.
PropertyInducingElement _getMemberField(
FieldElement _getMemberField(
InterfaceType type, PropertyAccessorElement member) {
String memberName = member.name;
PropertyInducingElement field;
FieldElement field;
if (member.isGetter) {
// The subclass member is an explicit getter or a field
// - lookup the getter on the superclass.
Expand Down Expand Up @@ -1388,9 +1389,10 @@ class _OverrideChecker {

if (isSubclass && element is PropertyAccessorElement) {
// Disallow any overriding if the base class defines this member
// as a field. We effectively treat fields as final / non-virtual.
PropertyInducingElement field = _getMemberField(type, element);
if (field != null) {
// as a field. We effectively treat fields as final / non-virtual,
// unless they are explicitly marked as @virtual
var field = _getMemberField(type, element);
if (field != null && !field.isVirtual) {
_checker._recordMessage(
errorLocation, StrongModeCode.INVALID_FIELD_OVERRIDE, [
element.enclosingElement.name,
Expand Down
37 changes: 26 additions & 11 deletions pkg/analyzer/lib/src/task/strong_mode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,7 @@ class InstanceMemberInferrer {
for (int i = 0; i < length; ++i) {
ParameterElement parameter = parameters[i];
if (parameter is ParameterElementImpl) {
// If a parameter is covariant, any parameters that override it are too.
parameter.inheritsCovariant = overriddenTypes.any((f) {
var param = _getCorrespondingParameter(parameter, i, f.parameters);
return param != null && param.isCovariant;
});
_inferParameterCovariance(parameter, i, overriddenTypes);

if (parameter.hasImplicitType) {
parameter.type = _computeParameterType(parameter, i, overriddenTypes);
Expand All @@ -327,9 +323,19 @@ class InstanceMemberInferrer {
* which no type was provided, infer the type of the field.
*/
void _inferField(FieldElement fieldElement) {
if (!fieldElement.isSynthetic &&
!fieldElement.isStatic &&
fieldElement.hasImplicitType) {
if (fieldElement.isSynthetic || fieldElement.isStatic) {
return;
}
List<ExecutableElement> overriddenSetters =
inheritanceManager.lookupOverrides(
fieldElement.enclosingElement, fieldElement.name + '=');
var setter = fieldElement.setter;
if (setter != null && overriddenSetters.isNotEmpty) {
_inferParameterCovariance(
setter.parameters[0], 0, overriddenSetters.map((s) => s.type));
}

if (fieldElement.hasImplicitType) {
//
// First look for overridden getters with the same name as the field.
//
Expand All @@ -339,9 +345,7 @@ class InstanceMemberInferrer {
if (overriddenGetters.isNotEmpty && _onlyGetters(overriddenGetters)) {
newType =
_computeReturnType(overriddenGetters.map((e) => e.returnType));
List<ExecutableElement> overriddenSetters =
inheritanceManager.lookupOverrides(
fieldElement.enclosingElement, fieldElement.name + '=');

if (!_isCompatible(newType, overriddenSetters)) {
newType = null;
}
Expand Down Expand Up @@ -375,6 +379,17 @@ class InstanceMemberInferrer {
}
}

/**
* If a parameter is covariant, any parameters that override it are too.
*/
void _inferParameterCovariance(ParameterElementImpl parameter, int index,
Iterable<FunctionType> overriddenTypes) {
parameter.inheritsCovariant = overriddenTypes.any((f) {
var param = _getCorrespondingParameter(parameter, index, f.parameters);
return param != null && param.isCovariant;
});
}

/**
* Infer type information for all of the instance members in the given
* interface [type].
Expand Down
45 changes: 44 additions & 1 deletion pkg/analyzer/test/src/task/strong/checker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,18 @@ class A {
get foo => '';
set foo(_) {}
}
class B extends A {
@checked int foo;
@checked num foo;
}
class C extends A {
@checked @virtual num foo;
}
class D extends C {
@virtual int foo;
}
class E extends D {
@virtual /*error:INVALID_METHOD_OVERRIDE*/num foo;
}
''');
}
Expand Down Expand Up @@ -752,6 +762,36 @@ class H implements F {
''');
}

void test_fieldOverride_virtual() {
_addMetaLibrary();
checkFile(r'''
import 'meta.dart';
class C {
@virtual int x;
}
class OverrideGetter extends C {
int get x => 42;
}
class OverrideSetter extends C {
set x(int v) {}
}
class OverrideBoth extends C {
int get x => 42;
set x(int v) {}
}
class OverrideWithField extends C {
int x;
// expose the hidden storage slot
int get superX => super.x;
set superX(int v) { super.x = v; }
}
class VirtualNotInherited extends OverrideWithField {
/*error:INVALID_FIELD_OVERRIDE*/int x;
}
''');
}

void test_fieldSetterOverride() {
checkFile('''
class A {}
Expand Down Expand Up @@ -3876,5 +3916,8 @@ void _addMetaLibrary() {
library meta;
class _Checked { const _Checked(); }
const Object checked = const _Checked();
class _Virtual { const _Virtual(); }
const Object virtual = const _Virtual();
''', name: '/meta.dart');
}
18 changes: 18 additions & 0 deletions pkg/meta/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## 1.0.4
* Introduce `@virtual` to allow field overrides in strong mode
(SDK issue [27384](https://github.com/dart-lang/sdk/issues/27384)).

```dart
import 'package:meta/meta.dart' show virtual;
class Base {
@virtual int x;
}
class Derived extends Base {
int x;
// Expose the hidden storage slot:
int get superX => super.x;
set superX(int v) { super.x = v; }
}
```
## 1.0.3
* Introduce `@checked` to override a method and tighten a parameter
type (SDK issue [25578](https://github.com/dart-lang/sdk/issues/25578)).
Expand Down
8 changes: 7 additions & 1 deletion pkg/meta/lib/meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ class Required {
const Required([this.reason]);
}


/// Used to annotate a parameter of an instance method that overrides another
/// method.
///
Expand All @@ -137,6 +136,9 @@ class Required {
/// is a subtype of the overridden parameter type.
const _Checked checked = const _Checked();

/// Used to annotate a field is allowed to be overridden in Strong Mode.
const _Virtual virtual = const _Virtual();

class _Checked {
const _Checked();
}
Expand All @@ -161,6 +163,10 @@ class _Protected {
const _Protected();
}

class _Virtual {
const _Virtual();
}

class _VisibleForTesting {
const _VisibleForTesting();
}
2 changes: 1 addition & 1 deletion pkg/meta/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: meta
version: 1.0.3
version: 1.0.4
author: Dart Team <[email protected]>
homepage: http://www.dartlang.org
description: >
Expand Down

0 comments on commit 865e808

Please sign in to comment.