Skip to content

Commit 0e09465

Browse files
author
John Messerly
committed
fix #25183, add support to ErrorVerifier for generic methods
[email protected], [email protected] Review URL: https://codereview.chromium.org/1514683002 .
1 parent e48f962 commit 0e09465

File tree

4 files changed

+207
-0
lines changed

4 files changed

+207
-0
lines changed

pkg/analyzer/lib/src/generated/error.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4868,6 +4868,35 @@ class StaticWarningCode extends ErrorCode {
48684868
const StaticWarningCode('INVALID_METHOD_OVERRIDE_NAMED_PARAM_TYPE',
48694869
"The parameter type '{0}' is not assignable to '{1}' as required by the method it is overriding from '{2}'");
48704870

4871+
/**
4872+
* Generic Method DEP: number of type parameters must match.
4873+
* <https://github.com/leafpetersen/dep-generic-methods/blob/master/proposal.md#function-subtyping>
4874+
*
4875+
* Parameters:
4876+
* 0: the number of type parameters in the method
4877+
* 1: the number of type parameters in the overridden method
4878+
* 2: the name of the class where the overridden method is declared
4879+
*/
4880+
static const StaticWarningCode INVALID_METHOD_OVERRIDE_TYPE_PARAMETERS =
4881+
const StaticWarningCode('INVALID_METHOD_OVERRIDE_TYPE_PARAMETERS',
4882+
"The method has {0} type parameters, but it is overriding a method with {1} type parameters from '{2}'");
4883+
4884+
/**
4885+
* Generic Method DEP: bounds of type parameters must be compatible.
4886+
* <https://github.com/leafpetersen/dep-generic-methods/blob/master/proposal.md#function-subtyping>
4887+
*
4888+
* Parameters:
4889+
* 0: the type parameter name
4890+
* 1: the type parameter bound
4891+
* 2: the overridden type parameter name
4892+
* 3: the overridden type parameter bound
4893+
* 4: the name of the class where the overridden method is declared
4894+
*/
4895+
static const StaticWarningCode INVALID_METHOD_OVERRIDE_TYPE_PARAMETER_BOUND =
4896+
const StaticWarningCode(
4897+
'INVALID_METHOD_OVERRIDE_TYPE_PARAMETER_BOUND',
4898+
"The type parameter '{0}' extends '{1}', but that is stricter than '{2}' extends '{3}' in the overridden method from '{4}'");
4899+
48714900
/**
48724901
* 7.1 Instance Methods: It is a static warning if an instance method
48734902
* <i>m1</i> overrides an instance method <i>m2</i> and the type of <i>m1</i>

pkg/analyzer/lib/src/generated/error_verifier.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,71 @@ class ErrorVerifier extends RecursiveAstVisitor<Object> {
13471347
if (overridingFT == null || overriddenFT == null) {
13481348
return false;
13491349
}
1350+
1351+
// Handle generic function type parameters.
1352+
// TODO(jmesserly): this duplicates some code in isSubtypeOf and most of
1353+
// _isGenericFunctionSubtypeOf. Ideally, we'd let TypeSystem produce
1354+
// an error message once it's ready to "return false".
1355+
if (!overridingFT.boundTypeParameters.isEmpty) {
1356+
if (overriddenFT.boundTypeParameters.isEmpty) {
1357+
overriddenFT = _typeSystem.instantiateToBounds(overriddenFT);
1358+
} else {
1359+
List<TypeParameterElement> params1 = overridingFT.boundTypeParameters;
1360+
List<TypeParameterElement> params2 = overriddenFT.boundTypeParameters;
1361+
int count = params1.length;
1362+
if (params2.length != count) {
1363+
_errorReporter.reportErrorForNode(
1364+
StaticWarningCode.INVALID_METHOD_OVERRIDE_TYPE_PARAMETERS,
1365+
errorNameTarget, [
1366+
count,
1367+
params2.length,
1368+
overriddenExecutable.enclosingElement.displayName
1369+
]);
1370+
return true;
1371+
}
1372+
// We build up a substitution matching up the type parameters
1373+
// from the two types, {variablesFresh/variables1} and
1374+
// {variablesFresh/variables2}
1375+
List<DartType> variables1 = new List<DartType>();
1376+
List<DartType> variables2 = new List<DartType>();
1377+
List<DartType> variablesFresh = new List<DartType>();
1378+
for (int i = 0; i < count; i++) {
1379+
TypeParameterElement p1 = params1[i];
1380+
TypeParameterElement p2 = params2[i];
1381+
TypeParameterElementImpl pFresh =
1382+
new TypeParameterElementImpl(p1.name, -1);
1383+
1384+
DartType variable1 = p1.type;
1385+
DartType variable2 = p2.type;
1386+
DartType variableFresh = new TypeParameterTypeImpl(pFresh);
1387+
1388+
variables1.add(variable1);
1389+
variables2.add(variable2);
1390+
variablesFresh.add(variableFresh);
1391+
DartType bound1 = p1.bound ?? DynamicTypeImpl.instance;
1392+
DartType bound2 = p2.bound ?? DynamicTypeImpl.instance;
1393+
bound1 = bound1.substitute2(variablesFresh, variables1);
1394+
bound2 = bound2.substitute2(variablesFresh, variables2);
1395+
pFresh.bound = bound2;
1396+
if (!_typeSystem.isSubtypeOf(bound2, bound1)) {
1397+
_errorReporter.reportErrorForNode(
1398+
StaticWarningCode.INVALID_METHOD_OVERRIDE_TYPE_PARAMETER_BOUND,
1399+
errorNameTarget, [
1400+
p1.displayName,
1401+
p1.bound,
1402+
p2.displayName,
1403+
p2.bound,
1404+
overriddenExecutable.enclosingElement.displayName
1405+
]);
1406+
return true;
1407+
}
1408+
}
1409+
// Proceed with the rest of the checks, using instantiated types.
1410+
overridingFT = overridingFT.instantiate(variablesFresh);
1411+
overriddenFT = overriddenFT.instantiate(variablesFresh);
1412+
}
1413+
}
1414+
13501415
DartType overridingFTReturnType = overridingFT.returnType;
13511416
DartType overriddenFTReturnType = overriddenFT.returnType;
13521417
List<DartType> overridingNormalPT = overridingFT.normalParameterTypes;

pkg/analyzer/lib/src/generated/type_system.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,15 @@ abstract class TypeSystem {
565565
DartType getLeastUpperBound(
566566
TypeProvider typeProvider, DartType type1, DartType type2);
567567

568+
/**
569+
* Given a [function] type, instantiate it with its bounds.
570+
*
571+
* The behavior of this method depends on the type system, for example, in
572+
* classic Dart `dynamic` will be used for all type arguments, whereas
573+
* strong mode prefers the actual bound type if it was specified.
574+
*/
575+
FunctionType instantiateToBounds(FunctionType function);
576+
568577
/**
569578
* Return `true` if the [leftType] is assignable to the [rightType] (that is,
570579
* if leftType <==> rightType).
@@ -673,6 +682,18 @@ class TypeSystemImpl implements TypeSystem {
673682
}
674683
}
675684

685+
/**
686+
* Instantiate the function type using `dynamic` for all generic parameters.
687+
*/
688+
FunctionType instantiateToBounds(FunctionType function) {
689+
int count = function.boundTypeParameters.length;
690+
if (count == 0) {
691+
return function;
692+
}
693+
return function.instantiate(
694+
new List<DartType>.filled(count, DynamicTypeImpl.instance));
695+
}
696+
676697
@override
677698
bool isAssignableTo(DartType leftType, DartType rightType) {
678699
return leftType.isAssignableTo(rightType);

pkg/analyzer/test/generated/resolver_test.dart

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12972,6 +12972,98 @@ class C<T> {
1297212972
expect(f.staticType.toString(), '<S>(S) → dynamic');
1297312973
}
1297412974

12975+
void test_genericMethod_override() {
12976+
_resolveTestUnit(r'''
12977+
class C {
12978+
/*=T*/ f/*<T>*/(/*=T*/ x) => null;
12979+
}
12980+
class D extends C {
12981+
/*=T*/ f/*<T>*/(/*=T*/ x) => null; // from D
12982+
}
12983+
''');
12984+
SimpleIdentifier f =
12985+
_findIdentifier('f/*<T>*/(/*=T*/ x) => null; // from D');
12986+
MethodElementImpl e = f.staticElement;
12987+
expect(e.typeParameters.toString(), '[T]');
12988+
expect(e.type.boundTypeParameters.toString(), '[T]');
12989+
expect(e.type.toString(), '<T>(T) → T');
12990+
12991+
FunctionType ft = e.type.instantiate([typeProvider.stringType]);
12992+
expect(ft.toString(), '(String) → String');
12993+
}
12994+
12995+
void test_genericMethod_override_bounds() {
12996+
_resolveTestUnit(r'''
12997+
class A {}
12998+
class B extends A {}
12999+
class C {
13000+
/*=T*/ f/*<T extends B>*/(/*=T*/ x) => null;
13001+
}
13002+
class D extends C {
13003+
/*=T*/ f/*<T extends A>*/(/*=T*/ x) => null;
13004+
}
13005+
''');
13006+
}
13007+
13008+
void test_genericMethod_override_invalidReturnType() {
13009+
Source source = addSource(r'''
13010+
class C {
13011+
Iterable/*<T>*/ f/*<T>*/(/*=T*/ x) => null;
13012+
}
13013+
class D extends C {
13014+
String f/*<S>*/(/*=S*/ x) => null;
13015+
}''');
13016+
// TODO(jmesserly): we can't use assertErrors because STRONG_MODE_* errors
13017+
// from CodeChecker don't have working equality.
13018+
List<AnalysisError> errors = analysisContext2.computeErrors(source);
13019+
expect(errors.map((e) => e.errorCode.name), [
13020+
'INVALID_METHOD_OVERRIDE_RETURN_TYPE',
13021+
'STRONG_MODE_INVALID_METHOD_OVERRIDE'
13022+
]);
13023+
expect(errors[0].message, contains('Iterable<S>'),
13024+
reason: 'errors should be in terms of the type parameters '
13025+
'at the error location');
13026+
verify([source]);
13027+
}
13028+
13029+
void test_genericMethod_override_invalidTypeParamBounds() {
13030+
Source source = addSource(r'''
13031+
class A {}
13032+
class B extends A {}
13033+
class C {
13034+
/*=T*/ f/*<T extends A>*/(/*=T*/ x) => null;
13035+
}
13036+
class D extends C {
13037+
/*=T*/ f/*<T extends B>*/(/*=T*/ x) => null;
13038+
}''');
13039+
// TODO(jmesserly): this is modified code from assertErrors, which we can't
13040+
// use directly because STRONG_MODE_* errors don't have working equality.
13041+
List<AnalysisError> errors = analysisContext2.computeErrors(source);
13042+
expect(errors.map((e) => e.errorCode.name), [
13043+
'STRONG_MODE_INVALID_METHOD_OVERRIDE',
13044+
'INVALID_METHOD_OVERRIDE_TYPE_PARAMETER_BOUND'
13045+
]);
13046+
verify([source]);
13047+
}
13048+
13049+
void test_genericMethod_override_invalidTypeParamCount() {
13050+
Source source = addSource(r'''
13051+
class C {
13052+
/*=T*/ f/*<T>*/(/*=T*/ x) => null;
13053+
}
13054+
class D extends C {
13055+
/*=S*/ f/*<T, S>*/(/*=T*/ x) => null;
13056+
}''');
13057+
// TODO(jmesserly): we can't use assertErrors because STRONG_MODE_* errors
13058+
// from CodeChecker don't have working equality.
13059+
List<AnalysisError> errors = analysisContext2.computeErrors(source);
13060+
expect(errors.map((e) => e.errorCode.name), [
13061+
'STRONG_MODE_INVALID_METHOD_OVERRIDE',
13062+
'INVALID_METHOD_OVERRIDE_TYPE_PARAMETERS'
13063+
]);
13064+
verify([source]);
13065+
}
13066+
1297513067
void test_pseudoGeneric_max_doubleDouble() {
1297613068
String code = r'''
1297713069
import 'dart:math';

0 commit comments

Comments
 (0)