Skip to content

Commit

Permalink
Implement inference_failure_on_function_invocation checks
Browse files Browse the repository at this point in the history
The "inference failure" checks implemented here are long overdue. They
are part of the strict-inference spec [1]. I think I caught most function
invocation cases. All of the work done to determine which error to report
and whether @optionalTypeArgs is annotated is done _after_ the check
for whether strict-inference is enabled, so this should have no effect
on code which does not opt in to that mode.

[1] https://github.com/dart-lang/language/blob/master/resources/type-system/strict-inference.md#function-call

Bug: #33749 and
Change-Id: Ic1d4321fb289acb118e0dbddd48ff917ad39d69a
#45371
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201321
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
  • Loading branch information
srawlins authored and commit-bot@chromium.org committed May 26, 2021
1 parent 6de639b commit fd8607c
Show file tree
Hide file tree
Showing 7 changed files with 405 additions and 5 deletions.
2 changes: 2 additions & 0 deletions pkg/analyzer/lib/error/error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,9 @@ const List<ErrorCode> errorCodeValues = [
HintCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION,
HintCode.IMPORT_OF_LEGACY_LIBRARY_INTO_NULL_SAFE,
HintCode.INFERENCE_FAILURE_ON_COLLECTION_LITERAL,
HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION,
HintCode.INFERENCE_FAILURE_ON_FUNCTION_RETURN_TYPE,
HintCode.INFERENCE_FAILURE_ON_GENERIC_INVOCATION,
HintCode.INFERENCE_FAILURE_ON_INSTANCE_CREATION,
HintCode.INFERENCE_FAILURE_ON_UNINITIALIZED_VARIABLE,
HintCode.INFERENCE_FAILURE_ON_UNTYPED_PARAMETER,
Expand Down
4 changes: 2 additions & 2 deletions pkg/analyzer/lib/src/dart/analysis/context_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ class ContextBuilderImpl implements ContextBuilder {
// AnalysisDriver reports results into streams.
// We need to drain these streams to avoid memory leak.
if (drainStreams) {
driver.results.drain();
driver.exceptions.drain();
driver.results.drain<void>();
driver.exceptions.drain<void>();
}

DriverBasedAnalysisContext context =
Expand Down
53 changes: 50 additions & 3 deletions pkg/analyzer/lib/src/dart/element/generic_inferrer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
import 'dart:math' as math;

import 'package:analyzer/dart/ast/ast.dart'
show Annotation, AstNode, ConstructorName;
show
Annotation,
AsExpression,
AstNode,
ConstructorName,
Expression,
InvocationExpression,
SimpleIdentifier;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart' show ErrorReporter;
Expand Down Expand Up @@ -488,6 +495,13 @@ class GenericInferrer {
if (errorReporter == null || errorNode == null) {
return;
}
if (errorNode.parent is InvocationExpression &&
errorNode.parent?.parent is AsExpression) {
// Casts via `as` do not play a part in downward inference. We allow an
// exception when inference has "failed" but the return value is
// immediately cast with `as`.
return;
}
if (errorNode is ConstructorName &&
!(errorNode.type.type as InterfaceType).element.hasOptionalTypeArgs) {
String constructorName = errorNode.name == null
Expand All @@ -511,9 +525,42 @@ class GenericInferrer {
[constructorName]);
}
}
} else if (errorNode is SimpleIdentifier) {
var element = errorNode.staticElement;
if (element != null) {
if (element is VariableElement) {
// For variable elements, we check their type and possible alias type.
var type = element.type;
var typeElement = type.element;
if (typeElement != null && typeElement.hasOptionalTypeArgs) {
return;
}
var typeAliasElement = type.aliasElement;
if (typeAliasElement != null &&
typeAliasElement.hasOptionalTypeArgs) {
return;
}
}
if (!element.hasOptionalTypeArgs) {
errorReporter.reportErrorForNode(
HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION,
errorNode,
[errorNode.name]);
return;
}
}
} else if (errorNode is Expression) {
var type = errorNode.staticType;
if (type != null) {
var typeDisplayString = type.getDisplayString(
withNullability: _typeSystem.isNonNullableByDefault);
errorReporter.reportErrorForNode(
HintCode.INFERENCE_FAILURE_ON_GENERIC_INVOCATION,
errorNode,
[typeDisplayString]);
return;
}
}
// TODO(srawlins): More inference failure cases, like functions, and
// function expressions.
}

/// If in a legacy library, return the legacy version of the [type].
Expand Down
19 changes: 19 additions & 0 deletions pkg/analyzer/lib/src/dart/error/hint_codes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,15 @@ class HintCode extends AnalyzerErrorCode {
"The type argument(s) of '{0}' can't be inferred.",
correction: "Use explicit type argument(s) for '{0}'.");

/**
* When "strict-inference" is enabled, types in function invocations must be
* inferred via the context type, or have type arguments.
*/
static const HintCode INFERENCE_FAILURE_ON_FUNCTION_INVOCATION = HintCode(
'INFERENCE_FAILURE_ON_FUNCTION_INVOCATION',
"The type argument(s) of the function '{0}' can't be inferred.",
correction: "Use explicit type argument(s) for '{0}'.");

/**
* When "strict-inference" is enabled, recursive local functions, top-level
* functions, methods, and function-typed function parameters must all
Expand All @@ -771,6 +780,16 @@ class HintCode extends AnalyzerErrorCode {
"The return type of '{0}' cannot be inferred.",
correction: "Declare the return type of '{0}'.");

/**
* When "strict-inference" is enabled, types in function invocations must be
* inferred via the context type, or have type arguments.
*/
static const HintCode INFERENCE_FAILURE_ON_GENERIC_INVOCATION = HintCode(
'INFERENCE_FAILURE_ON_GENERIC_INVOCATION',
"The type argument(s) of the generic function type '{0}' can't be "
"inferred.",
correction: "Use explicit type argument(s) for '{0}'.");

/**
* When "strict-inference" is enabled, types in instance creation
* (constructor calls) must be inferred via the context type, or have type
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// Copyright (c) 2021, 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/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../dart/resolution/context_collection_resolution.dart';

main() {
defineReflectiveSuite(() {
defineReflectiveTests(InferenceFailureOnFunctionInvocationTest);
});
}

/// Tests of HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION with the
/// "strict-inference" static analysis option.
@reflectiveTest
class InferenceFailureOnFunctionInvocationTest
extends PubPackageResolutionTest {
@override
void setUp() {
super.setUp();
writeTestPackageAnalysisOptionsFile(
AnalysisOptionsFileConfig(
strictInference: true,
),
);
writeTestPackageConfigWithMeta();
}

test_functionType_noInference() async {
await assertErrorsInCode('''
void f(void Function<T>() m) {
m();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 33, 1),
]);
}

test_functionType_notGeneric() async {
await assertNoErrorsInCode('''
void f(void Function() m) {
m();
}
''');
}

test_functionType_optionalTypeArgs() async {
await assertNoErrorsInCode('''
import 'package:meta/meta.dart';
void f(@optionalTypeArgs void Function<T>() m) {
m();
}
''');
}

test_genericFunctionExpression_explicitTypeArg() async {
await assertNoErrorsInCode('''
void f(void Function<T>()? m, void Function<T>() n) {
(m ?? n)<int>();
}
''');
}

test_genericMethod_downwardsInference() async {
await assertNoErrorsInCode('''
abstract class C {
T m<T>();
}
int f(C c) {
return c.m();
}
''');
}

test_genericMethod_explicitTypeArgs() async {
await assertNoErrorsInCode('''
abstract class C {
void m<T>();
}
void f(C c) {
c.m<int>();
}
''');
}

test_genericMethod_immediatelyCast() async {
await assertNoErrorsInCode('''
abstract class C {
T m<T>();
}
void f(C c) {
c.m() as int;
}
''');
}

test_genericMethod_noInference() async {
await assertErrorsInCode('''
abstract class C {
void m<T>();
}
void f(C c) {
c.m();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 55, 1),
]);
}

test_genericMethod_optionalTypeArgs() async {
await assertNoErrorsInCode('''
import 'package:meta/meta.dart';
abstract class C {
@optionalTypeArgs
void m<T>();
}
void f(C c) {
c.m();
}
''');
}

test_genericMethod_upwardsInference() async {
await assertNoErrorsInCode('''
abstract class C {
void m<T>(T a);
}
void f(C c) {
c.m(7);
}
''');
}

test_genericStaticMethod_noInference() async {
await assertErrorsInCode('''
class C {
static void m<T>() {}
}
void f() {
C.m();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 52, 1),
]);
}

test_genericTypedef_noInference() async {
await assertErrorsInCode('''
typedef Fn = void Function<T>();
void g(Fn fn) {
fn();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 51, 2),
]);
}

test_genericTypedef_optionalTypeArgs() async {
await assertNoErrorsInCode('''
import 'package:meta/meta.dart';
@optionalTypeArgs
typedef Fn = void Function<T>();
void g(Fn fn) {
fn();
}
''');
}

test_localFunction_noInference() async {
await assertErrorsInCode('''
void f() {
void g<T>() {}
g();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 30, 1),
]);
}

test_localFunctionVariable_noInference() async {
await assertErrorsInCode('''
void f() {
var m = <T>() {};
m();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 33, 1),
]);
}

test_nonGenericMethod() async {
await assertNoErrorsInCode('''
abstract class C {
void m();
}
void f(C c) {
c.m();
}
''');
}

test_topLevelFunction_noInference() async {
await assertErrorsInCode('''
void f<T>() {}
void g() {
f();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 29, 1),
]);
}

test_topLevelFunction_withImportPrefix_noInference() async {
newFile('$testPackageLibPath/a.dart', content: '''
void f<T>() {}
''');
await assertErrorsInCode('''
import 'a.dart' as a;
void g() {
a.f();
}
''', [
error(HintCode.INFERENCE_FAILURE_ON_FUNCTION_INVOCATION, 37, 1),
]);
}

test_topLevelFunction_withImportPrefix_optionalTypeArgs() async {
newFile('$testPackageLibPath/a.dart', content: '''
import 'package:meta/meta.dart';
@optionalTypeArgs
void f<T>() {}
''');
await assertNoErrorsInCode('''
import 'a.dart' as a;
void g() {
a.f();
}
''');
}
}
Loading

0 comments on commit fd8607c

Please sign in to comment.