Infer the return types of local functions where appropriate.

Note that we do this in order to be consistent with type inference of
function expressions.  See https://codereview.chromium.org/2209293002.

R=sigmund@google.com

Review-Url: https://codereview.chromium.org/2950213002 .
This commit is contained in:
Paul Berry 2017-06-22 12:17:08 -07:00
parent ec8505fc79
commit dd0a00f581
13 changed files with 255 additions and 175 deletions

View file

@ -369,6 +369,30 @@ class _InstrumentationVisitor extends RecursiveAstVisitor<Null> {
}
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);
if (node.element is LocalElement &&
node.element.enclosingElement is! CompilationUnitElement) {
if (node.returnType == null) {
_instrumentation.record(
uri,
node.name.offset,
'returnType',
new _InstrumentationValueForType(
node.element.returnType, elementNamer));
}
var parameters = node.functionExpression.parameters;
for (var parameter in parameters.parameters) {
// Note: it's tempting to check `parameter.type == null`, but that
// doesn't work because of function-typed formal parameter syntax.
if (parameter.element.hasImplicitType) {
_recordType(parameter.identifier.offset, parameter.element.type);
}
}
}
}
visitFunctionExpression(FunctionExpression node) {
super.visitFunctionExpression(node);
if (node.parent is! FunctionDeclaration) {

View file

@ -2432,10 +2432,14 @@ class BodyBuilder extends ScopeListener<JumpTarget> implements BuilderHelper {
FunctionNode function = pop();
exitLocalScope();
var declaration = pop();
var returnType = pop() ?? const DynamicType();
var returnType = pop();
var hasImplicitReturnType = returnType == null;
returnType ??= const DynamicType();
pop(); // Modifiers.
exitFunction();
if (declaration is FunctionDeclaration) {
KernelFunctionDeclaration.setHasImplicitReturnType(
declaration, hasImplicitReturnType);
function.returnType = returnType;
declaration.variable.type = function.functionType;
declaration.function = function;

View file

@ -25,11 +25,9 @@ import 'package:front_end/src/fasta/type_inference/type_inferrer.dart';
import 'package:front_end/src/fasta/type_inference/type_promotion.dart';
import 'package:front_end/src/fasta/type_inference/type_schema.dart';
import 'package:front_end/src/fasta/type_inference/type_schema_elimination.dart';
import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart';
import 'package:kernel/ast.dart'
hide InvalidExpression, InvalidInitializer, InvalidStatement;
import 'package:kernel/frontend/accessors.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import '../errors.dart' show internalError;
@ -715,31 +713,24 @@ class KernelForInStatement extends ForInStatement implements KernelStatement {
/// form.
class KernelFunctionDeclaration extends FunctionDeclaration
implements KernelStatement {
bool _hasImplicitReturnType = false;
KernelFunctionDeclaration(VariableDeclaration variable, FunctionNode function)
: super(variable, function);
@override
void _inferStatement(KernelTypeInferrer inferrer) {
inferrer.listener.functionDeclarationEnter(this);
for (var parameter in function.positionalParameters) {
if (parameter.initializer != null) {
inferrer.inferExpression(parameter.initializer, parameter.type, false);
}
}
for (var parameter in function.namedParameters) {
if (parameter.initializer != null) {
inferrer.inferExpression(parameter.initializer, parameter.type, false);
}
}
if (!inferrer.isTopLevel) {
var oldClosureContext = inferrer.closureContext;
inferrer.closureContext = new ClosureContext(
inferrer, function.asyncMarker, function.returnType);
inferrer.inferStatement(function.body);
inferrer.closureContext = oldClosureContext;
}
inferrer.inferLocalFunction(function, null, false, fileOffset,
_hasImplicitReturnType ? null : function.returnType, true);
variable.type = function.functionType;
inferrer.listener.functionDeclarationExit(this);
}
static void setHasImplicitReturnType(
KernelFunctionDeclaration declaration, bool hasImplicitReturnType) {
declaration._hasImplicitReturnType = hasImplicitReturnType;
}
}
/// Concrete shadow object representing a function expression in kernel form.
@ -774,137 +765,8 @@ class KernelFunctionExpression extends FunctionExpression
KernelTypeInferrer inferrer, DartType typeContext, bool typeNeeded) {
typeNeeded = inferrer.listener.functionExpressionEnter(this, typeContext) ||
typeNeeded;
if (!inferrer.isTopLevel) {
for (var parameter in function.positionalParameters) {
if (parameter.initializer != null) {
inferrer.inferExpression(
parameter.initializer, parameter.type, false);
}
}
for (var parameter in function.namedParameters) {
if (parameter.initializer != null) {
inferrer.inferExpression(
parameter.initializer, parameter.type, false);
}
}
}
// Let `<T0, ..., Tn>` be the set of type parameters of the closure (with
// `n`=0 if there are no type parameters).
List<TypeParameter> typeParameters = function.typeParameters;
// Let `(P0 x0, ..., Pm xm)` be the set of formal parameters of the closure
// (including required, positional optional, and named optional parameters).
// If any type `Pi` is missing, denote it as `_`.
List<VariableDeclaration> formals = function.positionalParameters.toList()
..addAll(function.namedParameters);
// Let `B` denote the closure body. If `B` is an expression function body
// (`=> e`), treat it as equivalent to a block function body containing a
// single `return` statement (`{ return e; }`).
// Attempt to match `K` as a function type compatible with the closure (that
// is, one having n type parameters and a compatible set of formal
// parameters). If there is a successful match, let `<S0, ..., Sn>` be the
// set of matched type parameters and `(Q0, ..., Qm)` be the set of matched
// formal parameter types, and let `N` be the return type.
Substitution substitution;
List<DartType> formalTypesFromContext =
new List<DartType>.filled(formals.length, null);
DartType returnContext;
if (inferrer.strongMode && typeContext is FunctionType) {
for (int i = 0; i < formals.length; i++) {
if (i < function.positionalParameters.length) {
formalTypesFromContext[i] =
getPositionalParameterType(typeContext, i);
} else {
formalTypesFromContext[i] =
getNamedParameterType(typeContext, formals[i].name);
}
}
returnContext = typeContext.returnType;
// Let `[T/S]` denote the type substitution where each `Si` is replaced with
// the corresponding `Ti`.
var substitutionMap = <TypeParameter, DartType>{};
for (int i = 0; i < typeContext.typeParameters.length; i++) {
substitutionMap[typeContext.typeParameters[i]] =
i < typeParameters.length
? new TypeParameterType(typeParameters[i])
: const DynamicType();
}
substitution = Substitution.fromMap(substitutionMap);
} else {
// If the match is not successful because `K` is `_`, let all `Si`, all
// `Qi`, and `N` all be `_`.
// If the match is not successful for any other reason, this will result in
// a type error, so the implementation is free to choose the best error
// recovery path.
substitution = Substitution.empty;
}
// Define `Ri` as follows: if `Pi` is not `_`, let `Ri` be `Pi`.
// Otherwise, if `Qi` is not `_`, let `Ri` be the greatest closure of
// `Qi[T/S]` with respect to `?`. Otherwise, let `Ri` be `dynamic`.
for (int i = 0; i < formals.length; i++) {
KernelVariableDeclaration formal = formals[i];
if (KernelVariableDeclaration.isImplicitlyTyped(formal)) {
DartType inferredType;
if (formalTypesFromContext[i] != null) {
inferredType = greatestClosure(inferrer.coreTypes,
substitution.substituteType(formalTypesFromContext[i]));
} else {
inferredType = const DynamicType();
}
inferrer.instrumentation?.record(
Uri.parse(inferrer.uri),
formal.fileOffset,
'type',
new InstrumentationValueForType(inferredType));
formal.type = inferredType;
}
}
// Let `N'` be `N[T/S]`. The [ClosureContext] constructor will adjust
// accordingly if the closure is declared with `async`, `async*`, or
// `sync*`.
if (returnContext != null) {
returnContext = substitution.substituteType(returnContext);
}
// Apply type inference to `B` in return context `N`, with any references
// to `xi` in `B` having type `Pi`. This produces `B`.
bool isExpressionFunction = function.body is ReturnStatement;
bool needToSetReturnType = isExpressionFunction || inferrer.strongMode;
ClosureContext oldClosureContext = inferrer.closureContext;
ClosureContext closureContext =
new ClosureContext(inferrer, function.asyncMarker, returnContext);
inferrer.closureContext = closureContext;
inferrer.inferStatement(function.body);
// If the closure is declared with `async*` or `sync*`, let `M` be the least
// upper bound of the types of the `yield` expressions in `B`, or `void` if
// `B` contains no `yield` expressions. Otherwise, let `M` be the least
// upper bound of the types of the `return` expressions in `B`, or `void`
// if `B` contains no `return` expressions.
DartType inferredReturnType;
if (needToSetReturnType || typeNeeded) {
inferredReturnType =
closureContext.inferReturnType(inferrer, isExpressionFunction);
}
// Then the result of inference is `<T0, ..., Tn>(R0 x0, ..., Rn xn) B` with
// type `<T0, ..., Tn>(R0, ..., Rn) -> M` (with some of the `Ri` and `xi`
// denoted as optional or named parameters, if appropriate).
if (needToSetReturnType) {
inferrer.instrumentation?.record(Uri.parse(inferrer.uri), fileOffset,
'returnType', new InstrumentationValueForType(inferredReturnType));
function.returnType = inferredReturnType;
}
inferrer.closureContext = oldClosureContext;
var inferredType = typeNeeded ? function.functionType : null;
var inferredType = inferrer.inferLocalFunction(
function, typeContext, typeNeeded, fileOffset, null, false);
inferrer.listener.functionExpressionExit(this, inferredType);
return inferredType;
}

View file

@ -22,6 +22,7 @@ import 'package:kernel/ast.dart'
DynamicType,
Expression,
Field,
FunctionNode,
FunctionType,
Initializer,
InterfaceType,
@ -33,10 +34,12 @@ import 'package:kernel/ast.dart'
ProcedureKind,
PropertyGet,
PropertySet,
ReturnStatement,
Statement,
SuperMethodInvocation,
SuperPropertyGet,
SuperPropertySet,
TypeParameter,
TypeParameterType,
VariableDeclaration,
VoidType;
@ -637,6 +640,136 @@ abstract class TypeInferrerImpl extends TypeInferrer {
return inferredType;
}
DartType inferLocalFunction(FunctionNode function, DartType typeContext,
bool typeNeeded, int fileOffset, DartType returnContext, bool isNamed) {
bool hasImplicitReturnType = returnContext == null;
if (!isTopLevel) {
for (var parameter in function.positionalParameters) {
if (parameter.initializer != null) {
inferExpression(parameter.initializer, parameter.type, false);
}
}
for (var parameter in function.namedParameters) {
if (parameter.initializer != null) {
inferExpression(parameter.initializer, parameter.type, false);
}
}
}
// Let `<T0, ..., Tn>` be the set of type parameters of the closure (with
// `n`=0 if there are no type parameters).
List<TypeParameter> typeParameters = function.typeParameters;
// Let `(P0 x0, ..., Pm xm)` be the set of formal parameters of the closure
// (including required, positional optional, and named optional parameters).
// If any type `Pi` is missing, denote it as `_`.
List<VariableDeclaration> formals = function.positionalParameters.toList()
..addAll(function.namedParameters);
// Let `B` denote the closure body. If `B` is an expression function body
// (`=> e`), treat it as equivalent to a block function body containing a
// single `return` statement (`{ return e; }`).
// Attempt to match `K` as a function type compatible with the closure (that
// is, one having n type parameters and a compatible set of formal
// parameters). If there is a successful match, let `<S0, ..., Sn>` be the
// set of matched type parameters and `(Q0, ..., Qm)` be the set of matched
// formal parameter types, and let `N` be the return type.
Substitution substitution;
List<DartType> formalTypesFromContext =
new List<DartType>.filled(formals.length, null);
if (strongMode && typeContext is FunctionType) {
for (int i = 0; i < formals.length; i++) {
if (i < function.positionalParameters.length) {
formalTypesFromContext[i] =
getPositionalParameterType(typeContext, i);
} else {
formalTypesFromContext[i] =
getNamedParameterType(typeContext, formals[i].name);
}
}
returnContext = typeContext.returnType;
// Let `[T/S]` denote the type substitution where each `Si` is replaced
// with the corresponding `Ti`.
var substitutionMap = <TypeParameter, DartType>{};
for (int i = 0; i < typeContext.typeParameters.length; i++) {
substitutionMap[typeContext.typeParameters[i]] =
i < typeParameters.length
? new TypeParameterType(typeParameters[i])
: const DynamicType();
}
substitution = Substitution.fromMap(substitutionMap);
} else {
// If the match is not successful because `K` is `_`, let all `Si`, all
// `Qi`, and `N` all be `_`.
// If the match is not successful for any other reason, this will result in
// a type error, so the implementation is free to choose the best error
// recovery path.
substitution = Substitution.empty;
}
// Define `Ri` as follows: if `Pi` is not `_`, let `Ri` be `Pi`.
// Otherwise, if `Qi` is not `_`, let `Ri` be the greatest closure of
// `Qi[T/S]` with respect to `?`. Otherwise, let `Ri` be `dynamic`.
for (int i = 0; i < formals.length; i++) {
KernelVariableDeclaration formal = formals[i];
if (KernelVariableDeclaration.isImplicitlyTyped(formal)) {
DartType inferredType;
if (formalTypesFromContext[i] != null) {
inferredType = greatestClosure(coreTypes,
substitution.substituteType(formalTypesFromContext[i]));
} else {
inferredType = const DynamicType();
}
instrumentation?.record(Uri.parse(uri), formal.fileOffset, 'type',
new InstrumentationValueForType(inferredType));
formal.type = inferredType;
}
}
// Let `N'` be `N[T/S]`. The [ClosureContext] constructor will adjust
// accordingly if the closure is declared with `async`, `async*`, or
// `sync*`.
if (returnContext != null) {
returnContext = substitution.substituteType(returnContext);
}
// Apply type inference to `B` in return context `N`, with any references
// to `xi` in `B` having type `Pi`. This produces `B`.
bool isExpressionFunction = function.body is ReturnStatement;
bool needToSetReturnType = hasImplicitReturnType &&
((isExpressionFunction && !isNamed) || strongMode);
ClosureContext oldClosureContext = this.closureContext;
ClosureContext closureContext =
new ClosureContext(this, function.asyncMarker, returnContext);
this.closureContext = closureContext;
inferStatement(function.body);
// If the closure is declared with `async*` or `sync*`, let `M` be the least
// upper bound of the types of the `yield` expressions in `B`, or `void` if
// `B` contains no `yield` expressions. Otherwise, let `M` be the least
// upper bound of the types of the `return` expressions in `B`, or `void`
// if `B` contains no `return` expressions.
DartType inferredReturnType;
if (needToSetReturnType || typeNeeded) {
inferredReturnType =
closureContext.inferReturnType(this, isExpressionFunction);
}
// Then the result of inference is `<T0, ..., Tn>(R0 x0, ..., Rn xn) B` with
// type `<T0, ..., Tn>(R0, ..., Rn) -> M` (with some of the `Ri` and `xi`
// denoted as optional or named parameters, if appropriate).
if (needToSetReturnType) {
instrumentation?.record(Uri.parse(uri), fileOffset, 'returnType',
new InstrumentationValueForType(inferredReturnType));
function.returnType = inferredReturnType;
}
this.closureContext = oldClosureContext;
return typeNeeded ? function.functionType : null;
}
/// Performs the core type inference algorithm for method invocations (this
/// handles both null-aware and non-null-aware method invocations).
DartType inferMethodInvocation(

View file

@ -244,6 +244,7 @@ inference/infer_generic_method_type_required: Crash
inference/infer_getter_cross_to_setter: Crash
inference/infer_getter_from_later_inferred_getter: Crash
inference/infer_list_literal_nested_in_map_literal: Crash
inference/infer_local_function_referenced_before_declaration: Crash
inference/infer_local_function_return_type: Crash
inference/infer_method_function_typed: Crash
inference/infer_method_missing_params: Crash

View file

@ -59,7 +59,6 @@ inference/downwards_inference_on_function_of_t_using_the_t: Fail
inference/future_then_explicit_future: Fail
inference/generic_functions_return_typedef: Fail
inference/generic_methods_infer_js_builtin: Fail
inference/infer_local_function_return_type: Fail
inference/infer_types_on_loop_indices_for_loop_with_inference: Fail
inference/infer_use_of_void: Fail
inference/list_literals_can_infer_null_top_level: Fail

View file

@ -0,0 +1,20 @@
// Copyright (c) 2017, 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.
/*@testedFeatures=inference*/
library test;
test() {
/*@returnType=dynamic*/ f() => /*error:REFERENCED_BEFORE_DECLARATION*/ g();
// Ignore inference for g since Fasta doesn't infer it due to the circularity,
// and that's ok.
/*@testedFeatures=none*/
g() => 0;
/*@testedFeatures=inference*/
var /*@type=() -> dynamic*/ v = f;
}
main() {}

View file

@ -0,0 +1,12 @@
library test;
import self as self;
import "dart:core" as core;
const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/inference/infer_local_function_referenced_before_declaration.dart:9:74: Error: Previous use of 'g'.\n /*@returnType=dynamic*/ f() => /*error:REFERENCED_BEFORE_DECLARATION*/ g();\n ^"]/* from null */;
static method test() → dynamic {
function f() → dynamic
return throw new core::NoSuchMethodError::_withType(null, #g, 32, <dynamic>[].toList(growable: false), <dynamic, dynamic>{}, null);
const core::_ConstantExpressionError::•()._throw(new core::_CompileTimeError::•("pkg/front_end/testcases/inference/infer_local_function_referenced_before_declaration.dart:14:3: Error: Can't declare 'g' because it was already used in this scope.\n g() => 0;\n ^"));
dynamic v = f;
}
static method main() → dynamic {}

View file

@ -0,0 +1,7 @@
library test;
import self as self;
static method test() → dynamic
;
static method main() → dynamic
;

View file

@ -0,0 +1,12 @@
library test;
import self as self;
import "dart:core" as core;
const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/inference/infer_local_function_referenced_before_declaration.dart:9:74: Error: Previous use of 'g'.\n /*@returnType=dynamic*/ f() => /*error:REFERENCED_BEFORE_DECLARATION*/ g();\n ^"]/* from null */;
static method test() → dynamic {
function f() → dynamic
return throw new core::NoSuchMethodError::_withType(null, #g, 32, <dynamic>[].toList(growable: false), <dynamic, dynamic>{}, null);
const core::_ConstantExpressionError::•()._throw(new core::_CompileTimeError::•("pkg/front_end/testcases/inference/infer_local_function_referenced_before_declaration.dart:14:3: Error: Can't declare 'g' because it was already used in this scope.\n g() => 0;\n ^"));
() → dynamic v = f;
}
static method main() → dynamic {}

View file

@ -6,30 +6,29 @@
library test;
test() {
f0() => 42;
f1() async => 42;
/*@returnType=int*/ f0() => 42;
/*@returnType=Future<int>*/ f1() async => 42;
f2() {
/*@returnType=int*/ f2() {
return 42;
}
f3() async {
/*@returnType=Future<int>*/ f3() async {
return 42;
}
f4() sync* {
/*@returnType=Iterable<int>*/ f4() sync* {
yield 42;
}
f5() async* {
/*@returnType=Stream<int>*/ f5() async* {
yield 42;
}
num f6() => 42;
f7() => f7();
f8() => /*error:REFERENCED_BEFORE_DECLARATION*/ f9();
f9() => f5();
/*@returnType=dynamic*/ f7() => f7();
/*@returnType=Stream<int>*/ f8() => f5();
var /*@type=() -> int*/ v0 = f0;
var /*@type=() -> Future<int>*/ v1 = f1;
@ -39,8 +38,7 @@ test() {
var /*@type=() -> Stream<int>*/ v5 = f5;
var /*@type=() -> num*/ v6 = f6;
var /*@type=() -> dynamic*/ v7 = f7;
var /*@type=() -> dynamic*/ v8 = f8;
var /*@type=() -> Stream<int>*/ v9 = f9;
var /*@type=() -> Stream<int>*/ v8 = f8;
}
main() {}

View file

@ -1,31 +1,39 @@
library test;
import self as self;
import "dart:core" as core;
import "dart:async" as asy;
const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/inference/infer_local_function_return_type.dart:31:51: Error: Previous use of 'f9'.\n f8() => /*error:REFERENCED_BEFORE_DECLARATION*/ f9();\n ^"]/* from null */;
static method test() → dynamic {
function f0() → dynamic
function f0() → core::int
return 42;
function f1() → dynamic async
function f1() → asy::Future<core::int> async
return 42;
function f2() → dynamic {
function f2() → core::int {
return 42;
}
function f3() → dynamic async {
function f3() → asy::Future<core::int> async {
return 42;
}
function f4() → dynamic sync* {
function f4() → core::Iterable<core::int> sync* {
yield 42;
}
function f5() → dynamic async* {
function f5() → asy::Stream<core::int> async* {
yield 42;
}
function f6() → core::num
return 42;
function f7() → dynamic
return f7.call();
function f8() → dynamic
return throw new core::NoSuchMethodError::_withType(null, #f9, 32, <dynamic>[].toList(growable: false), <dynamic, dynamic>{}, null);
const core::_ConstantExpressionError::•()._throw(new core::_CompileTimeError::•("pkg/front_end/testcases/inference/infer_local_function_return_type.dart:32:3: Error: Can't declare 'f9' because it was already used in this scope.\n f9() => f5();\n ^"));
function f8() → asy::Stream<core::int>
return f5.call();
() → core::int v0 = f0;
() → asy::Future<core::int> v1 = f1;
() → core::int v2 = f2;
() → asy::Future<core::int> v3 = f3;
() → core::Iterable<core::int> v4 = f4;
() → asy::Stream<core::int> v5 = f5;
() → core::num v6 = f6;
() → dynamic v7 = f7;
() → asy::Stream<core::int> v8 = f8;
}
static method main() → dynamic {}

View file

@ -15,8 +15,8 @@ void optional_toplevel([x = /*@typeArgs=int*/ const [0]]) {}
void named_toplevel({x: /*@typeArgs=int*/ const [0]}) {}
main() {
void optional_local([x = /*@typeArgs=int*/ const [0]]) {}
void named_local({x: /*@typeArgs=int*/ const [0]}) {}
void optional_local([/*@type=dynamic*/ x = /*@typeArgs=int*/ const [0]]) {}
void named_local({/*@type=dynamic*/ x: /*@typeArgs=int*/ const [0]}) {}
var /*@type=C<dynamic>*/ c_optional_toplevel =
new /*@typeArgs=dynamic*/ C.optional(optional_toplevel);
var /*@type=C<dynamic>*/ c_named_toplevel =