Skip to content

Commit 831c875

Browse files
author
John Messerly
committed
Refactor strong mode to use standard Analyzer errors
Also changes implicit casts errors to be determined by the type system isAssignableTo code, and updates our tests to show the user-facing error levels (StaticTypeWarningCode is upgraded to errors as far as users can see, but this wasn't visible in the tests, which showed these as "warnings"). found while looking at #26583 [email protected], [email protected] Review URL: https://codereview.chromium.org/2060013002 .
1 parent 678cb04 commit 831c875

13 files changed

+666
-820
lines changed

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

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2851,6 +2851,24 @@ abstract class ErrorCode {
28512851
StaticWarningCode.UNDEFINED_SUPER_SETTER,
28522852
StaticWarningCode.VOID_RETURN_FOR_GETTER,
28532853
StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH,
2854+
StrongModeCode.ASSIGNMENT_CAST,
2855+
StrongModeCode.DOWN_CAST_COMPOSITE,
2856+
StrongModeCode.DOWN_CAST_IMPLICIT,
2857+
StrongModeCode.DYNAMIC_CAST,
2858+
StrongModeCode.DYNAMIC_INVOKE,
2859+
StrongModeCode.INFERRED_TYPE,
2860+
StrongModeCode.INFERRED_TYPE_ALLOCATION,
2861+
StrongModeCode.INFERRED_TYPE_CLOSURE,
2862+
StrongModeCode.INFERRED_TYPE_LITERAL,
2863+
StrongModeCode.INVALID_FIELD_OVERRIDE,
2864+
StrongModeCode.INVALID_METHOD_OVERRIDE,
2865+
StrongModeCode.INVALID_METHOD_OVERRIDE_FROM_BASE,
2866+
StrongModeCode.INVALID_METHOD_OVERRIDE_FROM_MIXIN,
2867+
StrongModeCode.INVALID_PARAMETER_DECLARATION,
2868+
StrongModeCode.INVALID_SUPER_INVOCATION,
2869+
StrongModeCode.NON_GROUND_TYPE_CHECK_INFO,
2870+
StrongModeCode.STATIC_TYPE_ERROR,
2871+
28542872
TodoCode.TODO,
28552873

28562874
//
@@ -5806,6 +5824,117 @@ class StaticWarningCode extends ErrorCode {
58065824
ErrorType get type => ErrorType.STATIC_WARNING;
58075825
}
58085826

5827+
/**
5828+
* This class has Strong Mode specific error codes.
5829+
*
5830+
* These error codes tend to use the same message across different severity
5831+
* levels, so they are grouped for clarity.
5832+
*
5833+
* All of these error codes also use the "STRONG_MODE_" prefix in their name.
5834+
*/
5835+
class StrongModeCode extends ErrorCode {
5836+
static const String _implicitCastMessage =
5837+
'Unsound implicit cast from {0} to {1}';
5838+
5839+
static const String _typeCheckMessage =
5840+
'Type check failed: {0} is not of type {1}';
5841+
5842+
static const String _invalidOverrideMessage =
5843+
'The type of {0}.{1} ({2}) is not a '
5844+
'subtype of {3}.{1} ({4}).';
5845+
5846+
static const String _inferredTypeMessage = '{0} has inferred type {1}';
5847+
5848+
static const StrongModeCode DOWN_CAST_COMPOSITE = const StrongModeCode(
5849+
ErrorType.STATIC_WARNING, 'DOWN_CAST_COMPOSITE', _implicitCastMessage);
5850+
5851+
static const StrongModeCode DOWN_CAST_IMPLICIT = const StrongModeCode(
5852+
ErrorType.HINT, 'DOWN_CAST_IMPLICIT', _implicitCastMessage);
5853+
5854+
static const StrongModeCode DYNAMIC_CAST = const StrongModeCode(
5855+
ErrorType.HINT, 'DYNAMIC_CAST', _implicitCastMessage);
5856+
5857+
static const StrongModeCode ASSIGNMENT_CAST = const StrongModeCode(
5858+
ErrorType.HINT, 'ASSIGNMENT_CAST', _implicitCastMessage);
5859+
5860+
static const StrongModeCode INVALID_PARAMETER_DECLARATION =
5861+
const StrongModeCode(ErrorType.COMPILE_TIME_ERROR,
5862+
'INVALID_PARAMETER_DECLARATION', _typeCheckMessage);
5863+
5864+
static const StrongModeCode INFERRED_TYPE = const StrongModeCode(
5865+
ErrorType.HINT, 'INFERRED_TYPE', _inferredTypeMessage);
5866+
5867+
static const StrongModeCode INFERRED_TYPE_LITERAL = const StrongModeCode(
5868+
ErrorType.HINT, 'INFERRED_TYPE_LITERAL', _inferredTypeMessage);
5869+
5870+
static const StrongModeCode INFERRED_TYPE_ALLOCATION = const StrongModeCode(
5871+
ErrorType.HINT, 'INFERRED_TYPE_ALLOCATION', _inferredTypeMessage);
5872+
5873+
static const StrongModeCode INFERRED_TYPE_CLOSURE = const StrongModeCode(
5874+
ErrorType.HINT, 'INFERRED_TYPE_CLOSURE', _inferredTypeMessage);
5875+
5876+
static const StrongModeCode STATIC_TYPE_ERROR = const StrongModeCode(
5877+
ErrorType.COMPILE_TIME_ERROR,
5878+
'STATIC_TYPE_ERROR',
5879+
'Type check failed: {0} ({1}) is not of type {2}');
5880+
5881+
static const StrongModeCode INVALID_SUPER_INVOCATION = const StrongModeCode(
5882+
ErrorType.COMPILE_TIME_ERROR,
5883+
'INVALID_SUPER_INVOCATION',
5884+
"super call must be last in an initializer "
5885+
"list (see https://goo.gl/EY6hDP): {0}");
5886+
5887+
static const StrongModeCode NON_GROUND_TYPE_CHECK_INFO = const StrongModeCode(
5888+
ErrorType.HINT,
5889+
'NON_GROUND_TYPE_CHECK_INFO',
5890+
"Runtime check on non-ground type {0} may throw StrongModeError");
5891+
5892+
static const StrongModeCode DYNAMIC_INVOKE = const StrongModeCode(
5893+
ErrorType.HINT, 'DYNAMIC_INVOKE', '{0} requires a dynamic invoke');
5894+
5895+
static const StrongModeCode INVALID_METHOD_OVERRIDE = const StrongModeCode(
5896+
ErrorType.COMPILE_TIME_ERROR,
5897+
'INVALID_METHOD_OVERRIDE',
5898+
'Invalid override. $_invalidOverrideMessage');
5899+
5900+
static const StrongModeCode INVALID_METHOD_OVERRIDE_FROM_BASE =
5901+
const StrongModeCode(
5902+
ErrorType.COMPILE_TIME_ERROR,
5903+
'INVALID_METHOD_OVERRIDE_FROM_BASE',
5904+
'Base class introduces an invalid override. '
5905+
'$_invalidOverrideMessage');
5906+
5907+
static const StrongModeCode INVALID_METHOD_OVERRIDE_FROM_MIXIN =
5908+
const StrongModeCode(
5909+
ErrorType.COMPILE_TIME_ERROR,
5910+
'INVALID_METHOD_OVERRIDE_FROM_MIXIN',
5911+
'Mixin introduces an invalid override. $_invalidOverrideMessage');
5912+
5913+
static const StrongModeCode INVALID_FIELD_OVERRIDE = const StrongModeCode(
5914+
ErrorType.COMPILE_TIME_ERROR,
5915+
'INVALID_FIELD_OVERRIDE',
5916+
'Field declaration {3}.{1} cannot be '
5917+
'overridden in {0}.');
5918+
5919+
@override
5920+
final ErrorType type;
5921+
5922+
/**
5923+
* Initialize a newly created error code to have the given [type] and [name].
5924+
*
5925+
* The message associated with the error will be created from the given
5926+
* [message] template. The correction associated with the error will be
5927+
* created from the optional [correction] template.
5928+
*/
5929+
const StrongModeCode(ErrorType type, String name, String message,
5930+
[String correction])
5931+
: type = type,
5932+
super('STRONG_MODE_$name', message, correction);
5933+
5934+
@override
5935+
ErrorSeverity get errorSeverity => type.severity;
5936+
}
5937+
58095938
/**
58105939
* The error code indicating a marker in code for work that needs to be finished
58115940
* or revisited.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import 'package:analyzer/src/generated/sdk.dart' show DartSdk, SdkLibrary;
3030
import 'package:analyzer/src/generated/source.dart';
3131
import 'package:analyzer/src/generated/utilities_dart.dart';
3232
import 'package:analyzer/src/task/dart.dart';
33-
import 'package:analyzer/src/task/strong/info.dart' show StaticInfo;
33+
import 'package:analyzer/src/task/strong/checker.dart' as checker
34+
show isKnownFunction;
3435

3536
/**
3637
* A visitor used to traverse an AST structure looking for additional errors and
@@ -5743,8 +5744,7 @@ class ErrorVerifier extends RecursiveAstVisitor<Object> {
57435744

57445745
bool _expressionIsAssignableAtType(Expression expression,
57455746
DartType actualStaticType, DartType expectedStaticType) {
5746-
bool concrete =
5747-
_options.strongMode && StaticInfo.isKnownFunction(expression);
5747+
bool concrete = _options.strongMode && checker.isKnownFunction(expression);
57485748
if (concrete) {
57495749
actualStaticType =
57505750
_typeSystem.typeToConcreteType(_typeProvider, actualStaticType);

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ import 'package:analyzer/src/generated/source.dart';
3030
import 'package:analyzer/src/generated/static_type_analyzer.dart';
3131
import 'package:analyzer/src/generated/type_system.dart';
3232
import 'package:analyzer/src/generated/utilities_dart.dart';
33-
import 'package:analyzer/src/task/strong/info.dart'
34-
show InferredType, StaticInfo;
3533

3634
export 'package:analyzer/src/dart/resolver/inheritance_manager.dart';
3735
export 'package:analyzer/src/dart/resolver/scope.dart';
@@ -4646,7 +4644,7 @@ class InferenceContext {
46464644
/**
46474645
* The error listener on which to record inference information.
46484646
*/
4649-
final AnalysisErrorListener _errorListener;
4647+
final ErrorReporter _errorReporter;
46504648

46514649
/**
46524650
* If true, emit hints when types are inferred
@@ -4679,7 +4677,7 @@ class InferenceContext {
46794677
// https://github.com/dart-lang/sdk/issues/25322
46804678
final List<DartType> _returnStack = <DartType>[];
46814679

4682-
InferenceContext._(this._errorListener, TypeProvider typeProvider,
4680+
InferenceContext._(this._errorReporter, TypeProvider typeProvider,
46834681
this._typeSystem, this._inferenceHints)
46844682
: _typeProvider = typeProvider;
46854683

@@ -4758,12 +4756,22 @@ class InferenceContext {
47584756
* [type] has been inferred as the type of [node].
47594757
*/
47604758
void recordInference(Expression node, DartType type) {
4761-
StaticInfo info = InferredType.create(_typeSystem, node, type);
4762-
if (!_inferenceHints || info == null) {
4759+
if (!_inferenceHints) {
47634760
return;
47644761
}
4765-
AnalysisError error = info.toAnalysisError();
4766-
_errorListener.onError(error);
4762+
4763+
ErrorCode error;
4764+
if (node is Literal) {
4765+
error = StrongModeCode.INFERRED_TYPE_LITERAL;
4766+
} else if (node is InstanceCreationExpression) {
4767+
error = StrongModeCode.INFERRED_TYPE_ALLOCATION;
4768+
} else if (node is FunctionExpression) {
4769+
error = StrongModeCode.INFERRED_TYPE_CLOSURE;
4770+
} else {
4771+
error = StrongModeCode.INFERRED_TYPE;
4772+
}
4773+
4774+
_errorReporter.reportErrorForNode(error, node, [node, type]);
47674775
}
47684776

47694777
List<DartType> _matchTypes(InterfaceType t1, InterfaceType t2) {
@@ -5577,7 +5585,7 @@ class ResolverVisitor extends ScopedVisitor {
55775585
strongModeHints = options.strongModeHints;
55785586
}
55795587
this.inferenceContext = new InferenceContext._(
5580-
errorListener, typeProvider, typeSystem, strongModeHints);
5588+
errorReporter, typeProvider, typeSystem, strongModeHints);
55815589
this.typeAnalyzer = new StaticTypeAnalyzer(this);
55825590
}
55835591

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import 'package:analyzer/dart/element/element.dart';
1212
import 'package:analyzer/dart/element/type.dart';
1313
import 'package:analyzer/src/dart/element/element.dart';
1414
import 'package:analyzer/src/dart/element/type.dart';
15-
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
15+
import 'package:analyzer/src/generated/engine.dart'
16+
show AnalysisContext, AnalysisOptionsImpl;
1617
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
1718
import 'package:analyzer/src/generated/utilities_dart.dart';
1819

@@ -23,6 +24,15 @@ typedef bool _GuardedSubtypeChecker<T>(T t1, T t2, Set<Element> visited);
2324
* https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md
2425
*/
2526
class StrongTypeSystemImpl extends TypeSystem {
27+
/**
28+
* True if implicit casts should be allowed, otherwise false.
29+
*
30+
* This affects the behavior of [isAssignableTo].
31+
*/
32+
final bool implicitCasts;
33+
34+
StrongTypeSystemImpl({this.implicitCasts: true});
35+
2636
bool anyParameterType(FunctionType ft, bool predicate(DartType t)) {
2737
return ft.parameters.any((p) => predicate(p.type));
2838
}
@@ -290,6 +300,10 @@ class StrongTypeSystemImpl extends TypeSystem {
290300
return true;
291301
}
292302

303+
if (!implicitCasts) {
304+
return false;
305+
}
306+
293307
// Don't allow implicit downcasts between function types
294308
// and call method objects, as these will almost always fail.
295309
if ((fromType is FunctionType && getCallMethodType(toType) != null) ||
@@ -309,16 +323,13 @@ class StrongTypeSystemImpl extends TypeSystem {
309323
return false;
310324
}
311325

312-
// If the subtype relation goes the other way, allow the implicit downcast.
313-
// TODO(leafp): Emit warnings and hints for these in some way.
314-
// TODO(leafp): Consider adding a flag to disable these? Or just rely on
315-
// --warnings-as-errors?
326+
// If the subtype relation goes the other way, allow the implicit
327+
// downcast.
316328
if (isSubtypeOf(toType, fromType) || toType.isAssignableTo(fromType)) {
317-
// TODO(leafp): error if type is known to be exact (literal,
318-
// instance creation).
319-
// TODO(leafp): Warn on composite downcast.
320-
// TODO(leafp): hint on object/dynamic downcast.
321-
// TODO(leafp): Consider allowing assignment casts.
329+
// TODO(leafp,jmesserly): we emit warnings/hints for these in
330+
// src/task/strong/checker.dart, which is a bit inconsistent. That
331+
// code should be handled into places that use isAssignableTo, such as
332+
// ErrorVerifier.
322333
return true;
323334
}
324335

@@ -1180,8 +1191,9 @@ abstract class TypeSystem {
11801191
* Create either a strong mode or regular type system based on context.
11811192
*/
11821193
static TypeSystem create(AnalysisContext context) {
1183-
return (context.analysisOptions.strongMode)
1184-
? new StrongTypeSystemImpl()
1194+
var options = context.analysisOptions as AnalysisOptionsImpl;
1195+
return options.strongMode
1196+
? new StrongTypeSystemImpl(implicitCasts: options.implicitCasts)
11851197
: new TypeSystemImpl();
11861198
}
11871199
}

pkg/analyzer/lib/src/task/dart.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5682,9 +5682,12 @@ class StrongModeVerifyUnitTask extends SourceBasedAnalysisTask {
56825682
CompilationUnit unit = getRequiredInput(UNIT_INPUT);
56835683
AnalysisOptionsImpl options = context.analysisOptions;
56845684
if (options.strongMode) {
5685-
unit.accept(new CodeChecker(
5686-
typeProvider, new StrongTypeSystemImpl(), errorListener,
5687-
options));
5685+
CodeChecker checker = new CodeChecker(
5686+
typeProvider,
5687+
new StrongTypeSystemImpl(implicitCasts: options.implicitCasts),
5688+
errorListener,
5689+
options);
5690+
checker.visitCompilationUnit(unit);
56885691
}
56895692
//
56905693
// Record outputs.

pkg/analyzer/lib/src/task/options.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import 'package:analyzer/src/generated/java_engine.dart';
1515
import 'package:analyzer/src/generated/source.dart';
1616
import 'package:analyzer/src/generated/utilities_general.dart';
1717
import 'package:analyzer/src/task/general.dart';
18-
import 'package:analyzer/src/task/strong/info.dart';
1918
import 'package:analyzer/task/general.dart';
2019
import 'package:analyzer/task/model.dart';
2120
import 'package:source_span/source_span.dart';
@@ -161,8 +160,6 @@ class ErrorFilterOptionValidator extends OptionsValidator {
161160
_errorCodes = new HashSet<String>();
162161
// Engine codes.
163162
_errorCodes.addAll(ErrorCode.values.map((ErrorCode code) => code.name));
164-
// Strong-mode codes.
165-
_errorCodes.addAll(StaticInfo.names);
166163
}
167164
return _errorCodes;
168165
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Properties that result from Strong Mode analysis on an AST.
6+
///
7+
/// These properties are not public, but provided by use of back-ends such as
8+
/// Dart Dev Compiler.
9+
10+
import 'package:analyzer/analyzer.dart';
11+
import 'package:analyzer/dart/element/type.dart';
12+
13+
const String _implicitCast = '_implicitCast';
14+
const String _hasImplicitCasts = '_hasImplicitCasts';
15+
const String _isDynamicInvoke = '_isDynamicInvoke';
16+
17+
/// True if this compilation unit has any implicit casts, otherwise false.
18+
///
19+
/// See also [getImplicitCast].
20+
bool hasImplicitCasts(CompilationUnit node) {
21+
return node.getProperty/*<bool>*/(_hasImplicitCasts) ?? false;
22+
}
23+
24+
/// Sets [hasImplicitCasts] property for this compilation unit.
25+
void setHasImplicitCasts(CompilationUnit node, bool value) {
26+
node.setProperty(_hasImplicitCasts, value == true ? true : null);
27+
}
28+
29+
/// If this expression has an implicit cast, returns the type it is coerced to,
30+
/// otherwise returns null.
31+
DartType getImplicitCast(Expression node) {
32+
return node.getProperty/*<DartType>*/(_implicitCast);
33+
}
34+
35+
/// Sets the result of [getImplicitCast] for this node.
36+
void setImplicitCast(Expression node, DartType type) {
37+
node.setProperty(_implicitCast, type);
38+
}
39+
40+
/// True if this node is a dynamic operation that requires dispatch and/or
41+
/// checking at runtime.
42+
bool isDynamicInvoke(Expression node) {
43+
return node.getProperty/*<bool>*/(_isDynamicInvoke) ?? false;
44+
}
45+
46+
/// Sets [isDynamicInvoke] property for this expression
47+
void setIsDynamicInvoke(Expression node, bool value) {
48+
node.setProperty(_isDynamicInvoke, value == true ? true : null);
49+
}

0 commit comments

Comments
 (0)