Skip to content

Commit 8d2c6d0

Browse files
author
John Messerly
committed
fix #25220, infer generic type from constructor arguments
this makes it consistent in how constructors, generic functions, and literals are treated includes changes in https://codereview.chromium.org/2115743002/ [email protected] Review URL: https://codereview.chromium.org/2115173002 .
1 parent 056026f commit 8d2c6d0

File tree

8 files changed

+322
-51
lines changed

8 files changed

+322
-51
lines changed

pkg/analyzer/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## Next release
2+
* Strong mode breaking change: can now infer generic type arguments from the constructor invocation arguments (#25220).
3+
14
## 0.27.4-alpha.16
25
* (Internal) Corresponds with the analyzer/server in the `1.18.0-dev.4.0` SDK.
36

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6723,6 +6723,16 @@ class ResolverVisitor extends ScopedVisitor {
67236723
if (contextType is InterfaceType &&
67246724
contextType.typeArguments != null &&
67256725
contextType.typeArguments.length > 0) {
6726+
// TODO(jmesserly): for generic methods we use the
6727+
// StrongTypeSystemImpl.inferGenericFunctionCall, which appears to
6728+
// be a tad more powerful than matchTypes.
6729+
//
6730+
// For example it can infer this case:
6731+
//
6732+
// class E<S, T> extends A<C<S>, T> { ... }
6733+
// A<C<int>, String> a0 = /*infer<int, String>*/new E("hello");
6734+
//
6735+
// See _inferArgumentTypesFromContext in this file for use of it.
67266736
List<DartType> targs =
67276737
inferenceContext.matchTypes(classTypeName.type, contextType);
67286738
if (targs != null && targs.any((t) => !t.isDynamic)) {

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

Lines changed: 106 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:analyzer/dart/ast/visitor.dart';
1212
import 'package:analyzer/dart/element/element.dart';
1313
import 'package:analyzer/dart/element/type.dart';
1414
import 'package:analyzer/src/dart/element/element.dart';
15+
import 'package:analyzer/src/dart/element/member.dart' show ConstructorMember;
1516
import 'package:analyzer/src/dart/element/type.dart';
1617
import 'package:analyzer/src/generated/java_engine.dart';
1718
import 'package:analyzer/src/generated/resolver.dart';
@@ -521,7 +522,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
521522
@override
522523
Object visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
523524
if (_strongMode) {
524-
_inferGenericInvoke(node);
525+
_inferGenericInvocationExpression(node);
525526
}
526527
DartType staticType = _computeInvokeReturnType(node.staticInvokeType);
527528
_recordStaticType(node, staticType);
@@ -574,6 +575,10 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
574575
*/
575576
@override
576577
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
578+
if (_strongMode) {
579+
_inferInstanceCreationExpression(node);
580+
}
581+
577582
_recordStaticType(node, node.constructorName.type.type);
578583
ConstructorElement element = node.staticElement;
579584
if (element != null && "Element" == element.enclosingElement.name) {
@@ -812,7 +817,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
812817
SimpleIdentifier methodNameNode = node.methodName;
813818
Element staticMethodElement = methodNameNode.staticElement;
814819
if (_strongMode) {
815-
_inferGenericInvoke(node);
820+
_inferGenericInvocationExpression(node);
816821
}
817822
// Record types of the variable invoked as a function.
818823
if (staticMethodElement is VariableElement) {
@@ -1605,6 +1610,33 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
16051610
return returnType.type;
16061611
}
16071612

1613+
/**
1614+
* Given a constructor for a generic type, returns the equivalent generic
1615+
* function type that we could use to forward to the constructor, or for a
1616+
* non-generic type simply returns the constructor type.
1617+
*
1618+
* For example given the type `class C<T> { C(T arg); }`, the generic function
1619+
* type is `<T>(T) -> C<T>`.
1620+
*/
1621+
FunctionType _constructorToGenericFunctionType(
1622+
ConstructorElement constructor) {
1623+
// TODO(jmesserly): it may be worth making this available from the
1624+
// constructor. It's nice if our inference code can operate uniformly on
1625+
// function types.
1626+
ClassElement cls = constructor.enclosingElement;
1627+
FunctionType type = constructor.type;
1628+
if (cls.typeParameters.isEmpty) {
1629+
return type;
1630+
}
1631+
1632+
FunctionElementImpl function = new FunctionElementImpl("", -1);
1633+
function.synthetic = true;
1634+
function.returnType = type.returnType;
1635+
function.shareTypeParameters(cls.typeParameters);
1636+
function.shareParameters(type.parameters);
1637+
return function.type = new FunctionTypeImpl(function);
1638+
}
1639+
16081640
DartType _findIteratedType(DartType type, DartType targetType) {
16091641
// TODO(vsm): Use leafp's matchType here?
16101642
// Set by _find if match is found
@@ -1864,8 +1896,8 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
18641896
}
18651897

18661898
/**
1867-
* Given an uninstantiated generic type, try to infer the instantiated generic
1868-
* type from the surrounding context.
1899+
* Given an uninstantiated generic function type, try to infer the
1900+
* instantiated generic function type from the surrounding context.
18691901
*/
18701902
DartType _inferGenericInstantiationFromContext(
18711903
DartType context, DartType type) {
@@ -1878,14 +1910,40 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
18781910
return type;
18791911
}
18801912

1881-
bool _inferGenericInvoke(InvocationExpression node) {
1913+
/**
1914+
* Given a possibly generic invocation like `o.m(args)` or `(f)(args)` try to
1915+
* infer the instantiated generic function type.
1916+
*
1917+
* This takes into account both the context type, as well as information from
1918+
* the argument types.
1919+
*/
1920+
void _inferGenericInvocationExpression(InvocationExpression node) {
1921+
ArgumentList arguments = node.argumentList;
1922+
FunctionType inferred = _inferGenericInvoke(
1923+
node, node.function.staticType, node.typeArguments, arguments);
1924+
if (inferred != null && inferred != node.staticInvokeType) {
1925+
// Fix up the parameter elements based on inferred method.
1926+
arguments.correspondingStaticParameters = ResolverVisitor
1927+
.resolveArgumentsToParameters(arguments, inferred.parameters, null);
1928+
node.staticInvokeType = inferred;
1929+
}
1930+
}
1931+
1932+
/**
1933+
* Given a possibly generic invocation or instance creation, such as
1934+
* `o.m(args)` or `(f)(args)` or `new T(args)` try to infer the instantiated
1935+
* generic function type.
1936+
*
1937+
* This takes into account both the context type, as well as information from
1938+
* the argument types.
1939+
*/
1940+
FunctionType _inferGenericInvoke(Expression node, DartType fnType,
1941+
TypeArgumentList typeArguments, ArgumentList argumentList) {
18821942
TypeSystem ts = _typeSystem;
1883-
DartType fnType = node.function.staticType;
1884-
if (node.typeArguments == null &&
1943+
if (typeArguments == null &&
18851944
fnType is FunctionType &&
18861945
fnType.typeFormals.isNotEmpty &&
18871946
ts is StrongTypeSystemImpl) {
1888-
ArgumentList argumentList = node.argumentList;
18891947
// Get the parameters that correspond to the uninstantiated generic.
18901948
List<ParameterElement> rawParameters = ResolverVisitor
18911949
.resolveArgumentsToParameters(argumentList, fnType.parameters, null);
@@ -1900,20 +1958,48 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
19001958
}
19011959
}
19021960

1903-
FunctionType inferred = ts.inferGenericFunctionCall(_typeProvider, fnType,
1904-
paramTypes, argTypes, InferenceContext.getType(node));
1961+
return ts.inferGenericFunctionCall(_typeProvider, fnType, paramTypes,
1962+
argTypes, InferenceContext.getType(node));
1963+
}
1964+
return null;
1965+
}
19051966

1906-
if (inferred != node.staticInvokeType) {
1907-
// Fix up the parameter elements based on inferred method.
1908-
List<ParameterElement> inferredParameters =
1909-
ResolverVisitor.resolveArgumentsToParameters(
1910-
argumentList, inferred.parameters, null);
1911-
argumentList.correspondingStaticParameters = inferredParameters;
1912-
node.staticInvokeType = inferred;
1913-
return true;
1914-
}
1967+
/**
1968+
* Given an instance creation of a possibly generic type, infer the type
1969+
* arguments using the current context type as well as the argument types.
1970+
*/
1971+
void _inferInstanceCreationExpression(InstanceCreationExpression node) {
1972+
ConstructorName constructor = node.constructorName;
1973+
ConstructorElement originalElement = constructor.staticElement;
1974+
// If the constructor is generic, we'll have a ConstructorMember that
1975+
// substitutes in type arguments (possibly `dynamic`) from earlier in
1976+
// resolution.
1977+
//
1978+
// Otherwise we'll have a ConstructorElement, and we can skip inference
1979+
// because there's nothing to infer in a non-generic type.
1980+
if (originalElement is! ConstructorMember) {
1981+
return;
1982+
}
1983+
1984+
// Get back to the uninstantiated generic constructor.
1985+
// TODO(jmesserly): should we store this earlier in resolution?
1986+
// Or look it up, instead of jumping backwards through the Member?
1987+
var rawElement = (originalElement as ConstructorMember).baseElement;
1988+
1989+
FunctionType constructorType =
1990+
_constructorToGenericFunctionType(rawElement);
1991+
1992+
ArgumentList arguments = node.argumentList;
1993+
FunctionType inferred = _inferGenericInvoke(
1994+
node, constructorType, constructor.type.typeArguments, arguments);
1995+
1996+
if (inferred != null && inferred != originalElement.type) {
1997+
// Fix up the parameter elements based on inferred method.
1998+
arguments.correspondingStaticParameters = ResolverVisitor
1999+
.resolveArgumentsToParameters(arguments, inferred.parameters, null);
2000+
inferConstructorName(constructor, inferred.returnType);
2001+
// TODO(jmesserly): should we fix up the staticElement as well?
19152002
}
1916-
return false;
19172003
}
19182004

19192005
/**

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

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,7 @@ class StrongTypeSystemImpl extends TypeSystem {
167167
}
168168

169169
// Try to infer and instantiate the resulting type.
170-
var resultType =
171-
inferringTypeSystem._infer(fnType, allowPartialSolution: false);
170+
var resultType = inferringTypeSystem._infer(fnType);
172171

173172
// If the instantiation failed (because some type variable constraints
174173
// could not be solved, in other words, we could not find a valid subtype),
@@ -240,14 +239,7 @@ class StrongTypeSystemImpl extends TypeSystem {
240239
argumentTypes[i], correspondingParameterTypes[i]);
241240
}
242241

243-
// We are okay inferring some type variables and not others.
244-
//
245-
// This lets our return type be as precise as possible, which will help
246-
// make any type information resulting from it more precise.
247-
//
248-
// This is simply a heuristic: the code is incorrect, and we'll issue an
249-
// error on this call, to indicate that types don't match.
250-
return inferringTypeSystem._infer(fnType, allowPartialSolution: true);
242+
return inferringTypeSystem._infer(fnType);
251243
}
252244

253245
/**
@@ -1282,7 +1274,7 @@ class _StrongInferenceTypeSystem extends StrongTypeSystemImpl {
12821274

12831275
/// Given the constraints that were given by calling [isSubtypeOf], find the
12841276
/// instantiation of the generic function that satisfies these constraints.
1285-
FunctionType _infer(FunctionType fnType, {bool allowPartialSolution: false}) {
1277+
FunctionType _infer(FunctionType fnType) {
12861278
List<TypeParameterType> fnTypeParams =
12871279
TypeParameterTypeImpl.getTypes(fnType.typeFormals);
12881280

@@ -1335,16 +1327,8 @@ class _StrongInferenceTypeSystem extends StrongTypeSystemImpl {
13351327
bound.upper.substitute2(inferredTypes, fnTypeParams)) ||
13361328
!isSubtypeOf(bound.lower.substitute2(inferredTypes, fnTypeParams),
13371329
inferredTypes[i])) {
1338-
// Unless a partial solution was requested, bail.
1339-
if (!allowPartialSolution) {
1340-
return null;
1341-
}
1342-
1343-
inferredTypes[i] = DynamicTypeImpl.instance;
1344-
if (typeParam.element.bound != null) {
1345-
inferredTypes[i] =
1346-
typeParam.element.bound.substitute2(inferredTypes, fnTypeParams);
1347-
}
1330+
// Inference failed. Bail.
1331+
return null;
13481332
}
13491333
}
13501334

pkg/analyzer/test/generated/strong_mode_test.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,6 @@ class StrongModeDownwardsInferenceTest extends ResolverTestCase {
798798
A<int, String> a1 = new D.named(3);
799799
}
800800
void test8() {
801-
// Currently we only allow variable constraints. Test that we reject.
802801
A<C<int>, String> a0 = new E("hello");
803802
}
804803
void test9() { // Check named and optional arguments
@@ -921,7 +920,7 @@ class StrongModeDownwardsInferenceTest extends ResolverTestCase {
921920
{
922921
List<Statement> statements =
923922
AstFinder.getStatementsInTopLevelFunction(unit, "test8");
924-
hasType(assertEOf([_isDynamic, _isDynamic]), rhs(statements[0]));
923+
hasType(assertEOf([_isInt, _isString]), rhs(statements[0]));
925924
}
926925

927926
{

pkg/analyzer/test/generated/type_system_test.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -891,9 +891,7 @@ class StrongGenericFunctionInferenceTest {
891891
expect(_inferCall(clone, [foo.type, foo.type]), [foo.type]);
892892

893893
// Something invalid...
894-
expect(_inferCall(clone, [stringType, numType]), [
895-
clonable.type.instantiate([dynamicType])
896-
]);
894+
expect(_inferCall(clone, [stringType, numType]), null);
897895
}
898896

899897
void test_genericCastFunction() {
@@ -1007,7 +1005,7 @@ class StrongGenericFunctionInferenceTest {
10071005
// <T extends num>() -> T
10081006
var t = TypeBuilder.variable('T', bound: numType);
10091007
var f = TypeBuilder.function(types: [t], required: [], result: t);
1010-
expect(_inferCall(f, [], stringType), [numType]);
1008+
expect(_inferCall(f, [], stringType), null);
10111009
}
10121010

10131011
void test_unifyParametersToFunctionParam() {
@@ -1024,7 +1022,7 @@ class StrongGenericFunctionInferenceTest {
10241022
TypeBuilder.function(required: [intType], result: dynamicType),
10251023
TypeBuilder.function(required: [doubleType], result: dynamicType)
10261024
]),
1027-
[dynamicType]);
1025+
null);
10281026
}
10291027

10301028
void test_unusedReturnTypeIsDynamic() {
@@ -1045,7 +1043,7 @@ class StrongGenericFunctionInferenceTest {
10451043
[DartType returnType]) {
10461044
FunctionType inferred = typeSystem.inferGenericFunctionCall(typeProvider,
10471045
ft, ft.parameters.map((p) => p.type).toList(), arguments, returnType);
1048-
return inferred.typeArguments;
1046+
return inferred?.typeArguments;
10491047
}
10501048
}
10511049

pkg/analyzer/test/src/summary/resynthesize_ast_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,51 @@ class AstInferredTypeTest extends AbstractResynthesizeTest
155155
super.test_circularReference_viaClosures_initializerTypes();
156156
}
157157

158+
@override
159+
@failingTest
160+
void test_constructors_inferFromArguments() {
161+
// TODO(jmesserly): does this need to be implemented in AST summaries?
162+
// The test might need a change as well to not be based on local variable
163+
// types, which don't seem to be available.
164+
super.test_constructors_inferFromArguments();
165+
}
166+
167+
@override
168+
@failingTest
169+
void test_constructors_inferFromArguments_const() {
170+
super.test_constructors_inferFromArguments_const();
171+
}
172+
173+
@override
174+
@failingTest
175+
void test_constructors_inferFromArguments_factory() {
176+
super.test_constructors_inferFromArguments_factory();
177+
}
178+
179+
@override
180+
@failingTest
181+
void test_constructors_inferFromArguments_named() {
182+
super.test_constructors_inferFromArguments_named();
183+
}
184+
185+
@override
186+
@failingTest
187+
void test_constructors_inferFromArguments_namedFactory() {
188+
super.test_constructors_inferFromArguments_namedFactory();
189+
}
190+
191+
@override
192+
@failingTest
193+
void test_constructors_inferFromArguments_redirecting() {
194+
super.test_constructors_inferFromArguments_redirecting();
195+
}
196+
197+
@override
198+
@failingTest
199+
void test_constructors_inferFromArguments_redirectingFactory() {
200+
super.test_constructors_inferFromArguments_redirectingFactory();
201+
}
202+
158203
@override
159204
@failingTest
160205
void test_genericMethods_inferJSBuiltin() {

0 commit comments

Comments
 (0)