blob: c3a58396d83da95d1cac5966860aa57397a84129 [file] [log] [blame]
// Copyright (c) 2015, 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.
/// Defines static information collected by the type checker and used later by
/// emitters to generate code.
// TODO(jmesserly): this was ported from package:dev_compiler, and needs to be
// refactored to fit into analyzer.
library analyzer.src.task.strong.info;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/type_system.dart';
// A down cast due to a variable declaration to a ground type. E.g.,
// T x = expr;
// where T is ground. We exclude non-ground types as these behave differently
// compared to standard Dart.
class AssignmentCast extends DownCast {
AssignmentCast(TypeSystem rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
@override
String get name => 'STRONG_MODE_ASSIGNMENT_CAST';
toErrorCode() => new HintCode(name, message);
}
// Coercion which casts one type to another
class Cast extends Coercion {
Cast(DartType fromType, DartType toType) : super(fromType, toType);
}
// The abstract type of coercions mapping one type to another.
// This class also exposes static builder functions which
// check for errors and reduce redundant coercions to the identity.
abstract class Coercion {
final DartType fromType;
final DartType toType;
Coercion(this.fromType, this.toType);
static Coercion cast(DartType fromT, DartType toT) => new Cast(fromT, toT);
static Coercion error() => new CoercionError();
static Coercion identity(DartType type) => new Identity(type);
}
// The error coercion. This coercion signals that a coercion
// could not be generated. The code generator should not see
// these.
class CoercionError extends Coercion {
CoercionError() : super(null, null);
}
/// Implicitly injected expression conversion.
abstract class CoercionInfo extends StaticInfo {
static const String _propertyName = 'dev_compiler.src.info.CoercionInfo';
final TypeSystem rules;
final Expression node;
CoercionInfo(this.rules, this.node);
DartType get baseType => node.staticType ?? DynamicTypeImpl.instance;
DartType get convertedType;
String get message;
DartType get staticType => convertedType;
toErrorCode() => new HintCode(name, message);
/// Gets the coercion info associated with this node.
static CoercionInfo get(AstNode node) => node.getProperty(_propertyName);
/// Sets the coercion info associated with this node.
static CoercionInfo set(AstNode node, CoercionInfo info) {
node.setProperty(_propertyName, info);
return info;
}
}
// Base class for all casts from base type to sub type.
abstract class DownCast extends CoercionInfo {
Cast _cast;
DownCast._internal(TypeSystem rules, Expression expression, this._cast)
: super(rules, expression) {
assert(_cast.toType != baseType &&
_cast.fromType == baseType &&
(baseType.isDynamic ||
// Call methods make the following non-redundant
_cast.toType.isSubtypeOf(baseType) ||
baseType.isAssignableTo(_cast.toType)));
}
@override
List<Object> get arguments => [baseType, convertedType];
Cast get cast => _cast;
DartType get convertedType => _cast.toType;
@override
String get message => 'Unsound implicit cast from {0} to {1}';
// Factory to create correct DownCast variant.
static StaticInfo create(
StrongTypeSystemImpl rules, Expression expression, Cast cast) {
final fromT = cast.fromType;
final toT = cast.toType;
// toT <:_R fromT => to <: fromT
// NB: classes with call methods are subtypes of function
// types, but the function type is not assignable to the class
assert(toT.isSubtypeOf(fromT) || fromT.isAssignableTo(toT));
// Handle null call specially.
if (expression is NullLiteral) {
// TODO(vsm): Create a NullCast for this once we revisit nonnullability.
return new DownCastImplicit(rules, expression, cast);
}
// Inference "casts":
if (expression is Literal || expression is FunctionExpression) {
// fromT should be an exact type - this will almost certainly fail at
// runtime.
return new StaticTypeError(rules, expression, toT);
}
if (expression is InstanceCreationExpression) {
ConstructorElement e = expression.staticElement;
if (e == null || !e.isFactory) {
// fromT should be an exact type - this will almost certainly fail at
// runtime.
return new StaticTypeError(rules, expression, toT);
}
}
Element element = null;
if (expression is PropertyAccess) {
element = expression.propertyName.staticElement;
} else if (expression is Identifier) {
element = expression.staticElement;
}
// First class functions and static methods, where we know the original
// declaration, will have an exact type, so we know a downcast will fail.
if (element is FunctionElement ||
element is MethodElement && element.isStatic) {
return new StaticTypeError(rules, expression, toT);
}
// TODO(vsm): Change this to an assert when we have generic methods and
// fix TypeRules._coerceTo to disallow implicit sideways casts.
if (!rules.isSubtypeOf(toT, fromT)) {
assert(toT.isSubtypeOf(fromT) || fromT.isAssignableTo(toT));
return new DownCastComposite(rules, expression, cast);
}
// Composite cast: these are more likely to fail.
if (!rules.isGroundType(toT)) {
// This cast is (probably) due to our different treatment of dynamic.
// It may be more likely to fail at runtime.
if (fromT is InterfaceType) {
// For class types, we'd like to allow non-generic down casts, e.g.,
// Iterable<T> to List<T>. The intuition here is that raw (generic)
// casts are problematic, and we should complain about those.
var typeArgs = fromT.typeArguments;
if (typeArgs.isEmpty || typeArgs.any((t) => t.isDynamic)) {
return new DownCastComposite(rules, expression, cast);
}
} else {
return new DownCastComposite(rules, expression, cast);
}
}
// Dynamic cast
if (fromT.isDynamic) {
return new DynamicCast(rules, expression, cast);
}
// Assignment cast
var parent = expression.parent;
if (parent is VariableDeclaration && (parent.initializer == expression)) {
return new AssignmentCast(rules, expression, cast);
}
// Other casts
return new DownCastImplicit(rules, expression, cast);
}
}
//
// Implicit down casts. These are only injected by the compiler by flag.
//
// A down cast to a non-ground type. These behave differently from standard
// Dart and may be more likely to fail at runtime.
class DownCastComposite extends DownCast {
DownCastComposite(TypeSystem rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
@override
String get name => 'STRONG_MODE_DOWN_CAST_COMPOSITE';
toErrorCode() => new StaticTypeWarningCode(name, message);
}
// A down cast to a non-ground type. These behave differently from standard
// Dart and may be more likely to fail at runtime.
class DownCastImplicit extends DownCast {
DownCastImplicit(TypeSystem rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
@override
String get name => 'STRONG_MODE_DOWN_CAST_IMPLICIT';
toErrorCode() => new HintCode(name, message);
}
// A down cast from dynamic to T.
class DynamicCast extends DownCast {
DynamicCast(TypeSystem rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
@override
String get name => 'STRONG_MODE_DYNAMIC_CAST';
toErrorCode() => new HintCode(name, message);
}
class DynamicInvoke extends CoercionInfo {
static const String _propertyName = 'dev_compiler.src.info.DynamicInvoke';
DynamicInvoke(TypeSystem rules, Expression expression)
: super(rules, expression);
DartType get convertedType => DynamicTypeImpl.instance;
String get message => '{0} requires dynamic invoke';
@override
String get name => 'STRONG_MODE_DYNAMIC_INVOKE';
toErrorCode() => new HintCode(name, message);
/// Whether this [node] is the target of a dynamic operation.
static bool get(AstNode node) {
var value = node.getProperty(_propertyName);
return value != null ? value : false;
}
/// Sets whether this node is the target of a dynamic operation.
static bool set(AstNode node, bool value) {
// Free the storage for things that aren't dynamic.
if (value == false) value = null;
node.setProperty(_propertyName, value);
return value;
}
}
// The identity coercion
class Identity extends Coercion {
Identity(DartType fromType) : super(fromType, fromType);
}
// Standard / unspecialized inferred type
class InferredType extends InferredTypeBase {
InferredType(TypeSystem rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
@override
String get name => 'STRONG_MODE_INFERRED_TYPE';
// Factory to create correct InferredType variant.
static InferredTypeBase create(
TypeSystem rules, Expression expression, DartType type) {
// Specialized inference:
if (expression is Literal) {
return new InferredTypeLiteral(rules, expression, type);
}
if (expression is InstanceCreationExpression) {
return new InferredTypeAllocation(rules, expression, type);
}
if (expression is FunctionExpression) {
return new InferredTypeClosure(rules, expression, type);
}
return new InferredType(rules, expression, type);
}
}
// An inferred type for a non-literal allocation site.
class InferredTypeAllocation extends InferredTypeBase {
InferredTypeAllocation(TypeSystem rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
@override
String get name => 'STRONG_MODE_INFERRED_TYPE_ALLOCATION';
}
// An inferred type for the wrapped expression, which may need to be
// reified into the term
abstract class InferredTypeBase extends CoercionInfo {
final DartType _type;
InferredTypeBase._internal(
TypeSystem rules, Expression expression, this._type)
: super(rules, expression);
@override
List get arguments => [node, type];
DartType get convertedType => type;
@override
String get message => '{0} has inferred type {1}';
DartType get type => _type;
toErrorCode() => new HintCode(name, message);
}
// An inferred type for a closure expression
class InferredTypeClosure extends InferredTypeBase {
InferredTypeClosure(TypeSystem rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
@override
String get name => 'STRONG_MODE_INFERRED_TYPE_CLOSURE';
}
// An inferred type for a literal expression.
class InferredTypeLiteral extends InferredTypeBase {
InferredTypeLiteral(TypeSystem rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
@override
String get name => 'STRONG_MODE_INFERRED_TYPE_LITERAL';
}
class InvalidFieldOverride extends InvalidOverride {
InvalidFieldOverride(AstNode node, ExecutableElement element,
InterfaceType base, DartType subType, DartType baseType)
: super(node, element, base, subType, baseType);
String get message => 'Field declaration {3}.{1} cannot be '
'overridden in {0}.';
@override
String get name => 'STRONG_MODE_INVALID_FIELD_OVERRIDE';
}
// Invalid override due to incompatible type. I.e., the overridden signature
// is not compatible with the original.
class InvalidMethodOverride extends InvalidOverride {
InvalidMethodOverride(AstNode node, ExecutableElement element,
InterfaceType base, FunctionType subType, FunctionType baseType)
: super(node, element, base, subType, baseType);
String get message => _messageHelper('Invalid override');
@override
String get name => 'STRONG_MODE_INVALID_METHOD_OVERRIDE';
}
// Invalid override of an instance member of a class.
abstract class InvalidOverride extends StaticError {
/// Member declaration with the invalid override.
final ExecutableElement element;
/// Type (class or interface) that provides the base declaration.
final InterfaceType base;
/// Actual type of the overridden member.
final DartType subType;
/// Actual type of the base member.
final DartType baseType;
/// Whether the error comes from combining a base class and an interface
final bool fromBaseClass;
/// Whether the error comes from a mixin (either overriding a base class or an
/// interface declaration).
final bool fromMixin;
InvalidOverride(
AstNode node, this.element, this.base, this.subType, this.baseType)
: fromBaseClass = node is ExtendsClause,
fromMixin = node.parent is WithClause,
super(node);
@override
List<Object> get arguments =>
[parent.name, element.name, subType, base, baseType];
ClassElement get parent => element.enclosingElement;
String _messageHelper(String errorName) {
var lcErrorName = errorName.toLowerCase();
var intro = fromBaseClass
? 'Base class introduces an $lcErrorName'
: (fromMixin ? 'Mixin introduces an $lcErrorName' : errorName);
return '$intro. The type of {0}.{1} ({2}) is not a '
'subtype of {3}.{1} ({4}).';
}
}
class InvalidParameterDeclaration extends StaticError {
final DartType expectedType;
InvalidParameterDeclaration(
TypeSystem rules, FormalParameter declaration, this.expectedType)
: super(declaration);
@override
List<Object> get arguments => [node, expectedType];
@override
String get message => 'Type check failed: {0} is not of type {1}';
@override
String get name => 'STRONG_MODE_INVALID_PARAMETER_DECLARATION';
}
/// Dart constructors have one weird quirk, illustrated with this example:
///
/// class Base {
/// var x;
/// Base() : x = print('Base.1') {
/// print('Base.2');
/// }
/// }
///
/// class Derived extends Base {
/// var y, z;
/// Derived()
/// : y = print('Derived.1'),
/// super(),
/// z = print('Derived.2') {
/// print('Derived.3');
/// }
/// }
///
/// The order will be Derived.1, Base.1, Derived.2, Base.2, Derived.3; this
/// ordering preserves the invariant that code can't observe uninitialized
/// state, however it results in super constructor body not being run
/// immediately after super initializers. Normally this isn't observable, but it
/// could be if initializers have side effects.
///
/// Better to have `super` at the end, as required by the Dart style guide:
/// <https://goo.gl/EY6hDP>
///
/// For now this is the only pattern we support.
class InvalidSuperInvocation extends StaticError {
InvalidSuperInvocation(SuperConstructorInvocation node) : super(node);
@override
String get message => "super call must be last in an initializer "
"list (see https://goo.gl/EY6hDP): {0}";
@override
String get name => 'STRONG_MODE_INVALID_SUPER_INVOCATION';
}
class InvalidVariableDeclaration extends StaticError {
final DartType expectedType;
InvalidVariableDeclaration(
TypeSystem rules, AstNode declaration, this.expectedType)
: super(declaration);
@override
List<Object> get arguments => [expectedType];
@override
String get message => 'Type check failed: null is not of type {0}';
@override
String get name => 'STRONG_MODE_INVALID_VARIABLE_DECLARATION';
}
class NonGroundTypeCheckInfo extends StaticInfo {
final DartType type;
final AstNode node;
NonGroundTypeCheckInfo(this.node, this.type) {
assert(node is IsExpression || node is AsExpression);
}
@override
List<Object> get arguments => [type];
String get message =>
"Runtime check on non-ground type {0} may throw StrongModeError";
@override
String get name => 'STRONG_MODE_NON_GROUND_TYPE_CHECK_INFO';
toErrorCode() => new HintCode(name, message);
}
abstract class StaticError extends StaticInfo {
final AstNode node;
StaticError(this.node);
String get message;
toErrorCode() => new CompileTimeErrorCode(name, message);
}
// TODO(jmesserly): this could use some refactoring. These are essentially
// like ErrorCodes in analyzer, but we're including some details in our message.
// Analyzer instead has template strings, and replaces '{0}' with the first
// argument.
abstract class StaticInfo {
/// Strong-mode error code names.
///
/// Used for error code configuration validation in `.analysis_options`.
static const List<String> names = const [
//
// Manually populated.
//
'STRONG_MODE_ASSIGNMENT_CAST',
'STRONG_MODE_DOWN_CAST_COMPOSITE',
'STRONG_MODE_DOWN_CAST_IMPLICIT',
'STRONG_MODE_DYNAMIC_CAST',
'STRONG_MODE_DYNAMIC_INVOKE',
'STRONG_MODE_INFERRED_TYPE',
'STRONG_MODE_INFERRED_TYPE_ALLOCATION',
'STRONG_MODE_INFERRED_TYPE_CLOSURE',
'STRONG_MODE_INFERRED_TYPE_LITERAL',
'STRONG_MODE_INVALID_FIELD_OVERRIDE',
'STRONG_MODE_INVALID_METHOD_OVERRIDE',
'STRONG_MODE_INVALID_PARAMETER_DECLARATION',
'STRONG_MODE_INVALID_SUPER_INVOCATION',
'STRONG_MODE_INVALID_VARIABLE_DECLARATION',
'STRONG_MODE_NON_GROUND_TYPE_CHECK_INFO',
'STRONG_MODE_STATIC_TYPE_ERROR',
'STRONG_MODE_UNINFERRED_CLOSURE',
];
List<Object> get arguments => [node];
String get name;
/// AST Node this info is attached to.
AstNode get node;
AnalysisError toAnalysisError() {
int begin = node is AnnotatedNode
? (node as AnnotatedNode).firstTokenAfterCommentAndMetadata.offset
: node.offset;
int length = node.end - begin;
var source = (node.root as CompilationUnit).element.source;
return new AnalysisError(source, begin, length, toErrorCode(), arguments);
}
// TODO(jmesserly): review the usage of error codes. We probably want our own,
// as well as some DDC specific [ErrorType]s.
ErrorCode toErrorCode();
}
class StaticTypeError extends StaticError {
final DartType baseType;
final DartType expectedType;
StaticTypeError(TypeSystem rules, Expression expression, this.expectedType)
: baseType = expression.staticType ?? DynamicTypeImpl.instance,
super(expression);
@override
List<Object> get arguments => [node, baseType, expectedType];
@override
String get message => 'Type check failed: {0} ({1}) is not of type {2}';
@override
String get name => 'STRONG_MODE_STATIC_TYPE_ERROR';
}