blob: ec8626fd7e1ff93e6ffd2d8ff6c85277a6bb167f [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:
///
/// 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, DartType fromType,
DartType toType)
: super._internal(rules, expression, fromType, toType);
@override
String get name => 'STRONG_MODE_ASSIGNMENT_CAST';
toErrorCode() => new HintCode(name, message);
}
/// 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 {
final DartType _fromType;
final DartType _toType;
DownCast._internal(
TypeSystem rules, Expression expression, this._fromType, this._toType)
: super(rules, expression);
@override
List<Object> get arguments => [baseType, convertedType];
/// The type being cast from.
///
/// This is usually the static type of the associated expression, but may not
/// be if the cast is attached to a variable in a for-in loop.
@override
DartType get baseType => _fromType;
DartType get convertedType => _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,
DartType fromType, DartType 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(toType.isSubtypeOf(fromType) || fromType.isAssignableTo(toType));
// 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, toType);
}
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, toType);
}
}
if (StaticInfo.isKnownFunction(expression)) {
return new StaticTypeError(rules, expression, toType);
}
// TODO(vsm): Change this to an assert when we have generic methods and
// fix TypeRules._coerceTo to disallow implicit sideways casts.
if (!rules.isSubtypeOf(toType, fromType)) {
assert(toType.isSubtypeOf(fromType) || fromType.isAssignableTo(toType));
return new DownCastComposite(rules, expression, fromType, toType);
}
// Composite cast: these are more likely to fail.
if (!rules.isGroundType(toType)) {
// This cast is (probably) due to our different treatment of dynamic.
// It may be more likely to fail at runtime.
if (fromType 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 = fromType.typeArguments;
if (typeArgs.isEmpty || typeArgs.any((t) => t.isDynamic)) {
return new DownCastComposite(rules, expression, fromType, toType);
}
} else {
return new DownCastComposite(rules, expression, fromType, toType);
}
}
// Dynamic cast
if (fromType.isDynamic) {
return new DynamicCast(rules, expression, fromType, toType);
}
// Assignment cast
var parent = expression.parent;
if (parent is VariableDeclaration && (parent.initializer == expression)) {
return new AssignmentCast(rules, expression, fromType, toType);
}
// Other casts
return new DownCastImplicit(rules, expression, fromType, toType);
}
}
/// 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, DartType fromType,
DartType toType)
: super._internal(rules, expression, fromType, toType);
@override
String get name => 'STRONG_MODE_DOWN_CAST_COMPOSITE';
toErrorCode() => new StaticWarningCode(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, DartType fromType,
DartType toType)
: super._internal(rules, expression, fromType, toType);
@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, DartType fromType,
DartType toType)
: super._internal(rules, expression, fromType, toType);
@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) => node.getProperty(_propertyName) ?? 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;
}
}
/// 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 an analysis options file.
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();
static bool isKnownFunction(Expression expression) {
Element element = null;
if (expression is FunctionExpression) {
return true;
} else 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.
return element is FunctionElement ||
element is MethodElement && element.isStatic;
}
}
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';
}