Store inferred type arguments for tear-off(s).

This change passes pre-submit run over google3.
https://test.corp.google.com/ui#id=OCL:260993915:BASE:261043007:1564632838252:e80ace42

Change-Id: Ibef739f241fdb60a574da971961b1abe6abde9c8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/111402
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2019-08-01 18:25:39 +00:00
parent 5456641022
commit a05ef7c0cc
8 changed files with 216 additions and 91 deletions

View file

@ -4647,6 +4647,18 @@ abstract class SimpleIdentifier implements Identifier {
/// information to the given [element].
void set staticElement(Element element);
/// If the identifier is a tear-off, return the inferred type arguments
/// applied to the function type of the element to produce its [staticType].
///
/// Return an empty list if the function type does not have type parameters.
///
/// Return an empty list if the context type has type parameters.
///
/// Return `null` if not a tear-off.
///
/// Return `null` if the AST structure has not been resolved.
List<DartType> get tearOffTypeArgumentTypes;
/// Return the token representing the identifier.
Token get token;

View file

@ -8902,6 +8902,9 @@ class SimpleIdentifierImpl extends IdentifierImpl implements SimpleIdentifier {
/// getter context.
AuxiliaryElements auxiliaryElements = null;
@override
List<DartType> tearOffTypeArgumentTypes;
/// Initialize a newly created identifier.
SimpleIdentifierImpl(this.token);

View file

@ -6386,6 +6386,8 @@ class ResolutionCopier implements AstVisitor<bool> {
toNode.staticElement = node.staticElement;
toNode.staticType = node.staticType;
toNode.auxiliaryElements = node.auxiliaryElements;
(toNode as SimpleIdentifierImpl).tearOffTypeArgumentTypes =
node.tearOffTypeArgumentTypes;
return true;
}
return false;

View file

@ -831,7 +831,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
staticType = staticElement.type;
}
staticType = _inferGenericInstantiationFromContext(node, staticType);
staticType = _inferTearOff(node, node.identifier, staticType);
if (!_inferObjectAccess(node, staticType, prefixedIdentifier)) {
_recordStaticType(prefixedIdentifier, staticType);
_recordStaticType(node, staticType);
@ -924,7 +924,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
_nonNullableEnabled) {
staticType = _typeSystem.makeNullable(staticType);
}
staticType = _inferGenericInstantiationFromContext(node, staticType);
staticType = _inferTearOff(node, node.propertyName, staticType);
if (!_inferObjectAccess(node, staticType, propertyName)) {
_recordStaticType(propertyName, staticType);
@ -1074,7 +1074,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
} else {
staticType = _dynamicType;
}
staticType = _inferGenericInstantiationFromContext(node, staticType);
staticType = _inferTearOff(node, node, staticType);
_recordStaticType(node, staticType);
}
@ -1567,23 +1567,6 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
}
}
/**
* Given an uninstantiated generic function type, try to infer the
* instantiated generic function type from the surrounding context.
*/
DartType _inferGenericInstantiationFromContext(AstNode node, DartType type) {
TypeSystem ts = _typeSystem;
var context = InferenceContext.getContext(node);
if (context is FunctionType &&
type is FunctionType &&
ts is Dart2TypeSystem) {
// TODO(scheglov) Also store type arguments for identifiers.
return ts.inferFunctionTypeInstantiation(context, type,
errorReporter: _resolver.errorReporter, errorNode: node);
}
return type;
}
/**
* Given a possibly generic invocation like `o.m(args)` or `(f)(args)` try to
* infer the instantiated generic function type.
@ -1929,6 +1912,36 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
}
}
/**
* Given an uninstantiated generic function type, referenced by the
* [identifier] in the tear-off [expression], try to infer the instantiated
* generic function type from the surrounding context.
*/
DartType _inferTearOff(
Expression expression,
SimpleIdentifier identifier,
DartType tearOffType,
) {
TypeSystem ts = _typeSystem;
var context = InferenceContext.getContext(expression);
if (context is FunctionType &&
tearOffType is FunctionType &&
ts is Dart2TypeSystem) {
var typeArguments = ts.inferFunctionTypeInstantiation(
context,
tearOffType,
errorReporter: _resolver.errorReporter,
errorNode: expression,
);
(identifier as SimpleIdentifierImpl).tearOffTypeArgumentTypes =
typeArguments;
if (typeArguments.isNotEmpty) {
return tearOffType.instantiate(typeArguments);
}
}
return tearOffType;
}
/**
* Return `true` if the given [node] is not a type literal.
*/

View file

@ -202,18 +202,18 @@ class Dart2TypeSystem extends TypeSystem {
* Given a generic function type `F<T0, T1, ... Tn>` and a context type C,
* infer an instantiation of F, such that `F<S0, S1, ..., Sn>` <: C.
*
* This is similar to [inferGenericFunctionOrType], but the return type is also
* considered as part of the solution.
* This is similar to [inferGenericFunctionOrType2], but the return type is
* also considered as part of the solution.
*
* If this function is called with a [contextType] that is also
* uninstantiated, or a [fnType] that is already instantiated, it will have
* no effect and return [fnType].
* no effect and return `null`.
*/
FunctionType inferFunctionTypeInstantiation(
List<DartType> inferFunctionTypeInstantiation(
FunctionType contextType, FunctionType fnType,
{ErrorReporter errorReporter, AstNode errorNode}) {
if (contextType.typeFormals.isNotEmpty || fnType.typeFormals.isEmpty) {
return fnType;
return const <DartType>[];
}
// Create a TypeSystem that will allow certain type parameters to be
@ -224,12 +224,11 @@ class Dart2TypeSystem extends TypeSystem {
inferrer.constrainGenericFunctionInContext(fnType, contextType);
// Infer and instantiate the resulting type.
var inferredTypes = inferrer.infer(
return inferrer.infer(
fnType.typeFormals,
errorReporter: errorReporter,
errorNode: errorNode,
);
return fnType.instantiate(inferredTypes);
}
/// Infers a generic type, function, method, or list/map literal

View file

@ -0,0 +1,145 @@
// Copyright (c) 2019, 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.
import 'package:analyzer/dart/element/element.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../driver_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(TearOffTest);
});
}
@reflectiveTest
class TearOffTest extends DriverResolutionTest {
test_empty_contextNotInstantiated() async {
await assertNoErrorsInCode('''
T f<T>(T x) => x;
void test() {
U Function<U>(U) context;
context = f; // 1
}
''');
_assertTearOff(
'f; // 1',
findElement.topFunction('f'),
'T Function<T>(T)',
[],
);
}
test_empty_notGeneric() async {
await assertNoErrorsInCode('''
int f(int x) => x;
void test() {
int Function(int) context;
context = f; // 1
}
''');
_assertTearOff(
'f; // 1',
findElement.topFunction('f'),
'int Function(int)',
[],
);
}
test_notEmpty() async {
await assertNoErrorsInCode('''
T f<T>(T x) => x;
class C {
T f<T>(T x) => x;
static T g<T>(T x) => x;
}
class D extends C {
void test() {
int Function(int) func;
func = super.f; // 1
}
}
void test() {
T h<T>(T x) => x;
int Function(int) func;
func = f; // 2
func = new C().f; // 3
func = C.g; // 4
func = h; // 5
}
''');
_assertTearOff(
'f; // 1',
findElement.method('f', of: 'C'),
'int Function(int)',
['int'],
);
_assertTearOff(
'f; // 2',
findElement.topFunction('f'),
'int Function(int)',
['int'],
);
_assertTearOff(
'f; // 3',
findElement.method('f', of: 'C'),
'int Function(int)',
['int'],
);
_assertTearOff(
'g; // 4',
findElement.method('g', of: 'C'),
'int Function(int)',
['int'],
);
_assertTearOff(
'h; // 5',
findElement.localFunction('h'),
'int Function(int)',
['int'],
);
}
test_null_notTearOff() async {
await assertNoErrorsInCode('''
T f<T>(T x) => x;
void test() {
f(0);
}
''');
_assertTearOff(
'f(0);',
findElement.topFunction('f'),
'T Function<T>(T)',
null,
);
assertInvokeType(
findNode.methodInvocation('f(0)'),
'int Function(int)',
);
}
void _assertTearOff(
String search,
ExecutableElement element,
String type,
List<String> typeArguments,
) {
var id = findNode.simple(search);
assertElement(id, element);
assertType(id, type);
if (typeArguments != null) {
assertElementTypeStrings(id.tearOffTypeArgumentTypes, typeArguments);
} else {
expect(id.tearOffTypeArgumentTypes, isNull);
}
}
}

View file

@ -13,6 +13,7 @@ import 'map_literal_test.dart' as map_literal;
import 'prefix_expressions_test.dart' as prefix_expressions;
import 'set_literal_test.dart' as set_literal;
import 'statements_test.dart' as statements;
import 'tear_off_test.dart' as tear_off;
import 'throw_test.dart' as throw_expression;
import 'type_test_expressions_test.dart' as type_test_expressions;
@ -27,6 +28,7 @@ main() {
prefix_expressions.main();
set_literal.main();
statements.main();
tear_off.main();
throw_expression.main();
type_test_expressions.main();
}, name: 'type inference');

View file

@ -2999,7 +2999,7 @@ class CodeGenerator extends Object
@override
js_ast.Expression visitSimpleIdentifier(SimpleIdentifier node,
[PrefixedIdentifier prefix]) {
var typeArgs = _getTypeArgs(node.staticElement, node.staticType);
var typeArgs = _emitFunctionTypeArguments(node.tearOffTypeArgumentTypes);
var simpleId = _emitSimpleIdentifier(node, prefix)
..sourceInformation = _nodeSpan(node);
if (prefix != null &&
@ -3991,57 +3991,18 @@ class CodeGenerator extends Object
if (function is Identifier && !_reifyGeneric(function.staticElement)) {
return null;
}
return _emitFunctionTypeArguments(
node, function.staticType, node.staticInvokeType, node.typeArguments);
if (node.typeArgumentTypes.isEmpty) {
return null;
}
return _emitFunctionTypeArguments(node.typeArgumentTypes);
}
/// If `g` is a generic function type, and `f` is an instantiation of it,
/// then this will return the type arguments to apply, otherwise null.
/// If has [typeArguments], emit them, otherwise return `null`.
List<js_ast.Expression> _emitFunctionTypeArguments(
AstNode node, DartType g, DartType f,
[TypeArgumentList typeArgs]) {
if (node is InvocationExpression) {
if (g is! FunctionType && typeArgs == null) {
return null;
}
var typeArguments = node.typeArgumentTypes;
return typeArguments.map(_emitType).toList(growable: false);
}
if (g is FunctionType &&
g.typeFormals.isNotEmpty &&
f is FunctionType &&
f.typeFormals.isEmpty) {
var typeArguments = _recoverTypeArguments(g, f);
return typeArguments.map(_emitType).toList(growable: false);
}
return null;
}
/// Given a generic function type [g] and an instantiated function type [f],
/// find a list of type arguments TArgs such that `g<TArgs> == f`,
/// and return TArgs.
///
/// This function must be called with type [f] that was instantiated from [g].
Iterable<DartType> _recoverTypeArguments(FunctionType g, FunctionType f) {
// TODO(jmesserly): this design is a bit unfortunate. It would be nice if
// resolution could simply create a synthetic type argument list.
assert(g.typeFormals.isNotEmpty && f.typeFormals.isEmpty);
assert(g.typeFormals.length <= f.typeArguments.length);
// Instantiation in Analyzer works like this:
// Given:
// {U/T} <S> T -> S
// Where {U/T} represents the typeArguments (U) and typeParameters (T) list,
// and <S> represents the typeFormals.
//
// Now instantiate([V]), and the result should be:
// {U/T, V/S} T -> S.
//
// Therefore, we can recover the typeArguments from our instantiated
// function.
return f.typeArguments.skip(f.typeArguments.length - g.typeFormals.length);
List<DartType> typeArguments) {
if (typeArguments == null) return null;
if (typeArguments.isEmpty) return null;
return typeArguments.map(_emitType).toList(growable: false);
}
/// Emits code for the `JS(...)` macro.
@ -5260,23 +5221,9 @@ class CodeGenerator extends Object
}
}
List<js_ast.Expression> _getTypeArgs(Element member, DartType instantiated) {
DartType type;
if (member is ExecutableElement) {
type = member.type;
} else if (member is VariableElement) {
type = member.type;
}
// TODO(jmesserly): handle explicitly passed type args.
if (type == null) return null;
return _emitFunctionTypeArguments(null, type, instantiated);
}
/// Shared code for [PrefixedIdentifier] and [PropertyAccess].
js_ast.Expression _emitPropertyGet(
Expression receiver, SimpleIdentifier memberId, Expression accessNode) {
var resultType = accessNode.staticType;
var accessor = memberId.staticElement;
var memberName = memberId.name;
var receiverType = getStaticType(receiver);
@ -5329,7 +5276,9 @@ class CodeGenerator extends Object
result = _emitTargetAccess(jsTarget, jsName, accessor, memberId);
}
var typeArgs = _getTypeArgs(accessor, resultType);
var typeArgs = _emitFunctionTypeArguments(
memberId.tearOffTypeArgumentTypes,
);
return typeArgs == null
? result
: runtimeCall('gbind(#, #)', [result, typeArgs]);