| // 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. |
| |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
| import 'package:analyzer/src/generated/type_system.dart' |
| show StrongTypeSystemImpl; |
| |
| /// An abstraction of the JS types |
| abstract class JSType { |
| const JSType(); |
| |
| /// True if this type is built-in to JS, and we use the values unwrapped. |
| /// For these types we generate a calling convention via static |
| /// "extension methods". This allows types to be extended without adding |
| /// extensions directly on the prototype. |
| bool get isPrimitive; |
| |
| /// Is this type known to be definitively primitive |
| /// (using the JS notion of primitive) |
| bool get isPrimitiveInJS; |
| |
| /// Can a non-null element of this type potentially be interpreted |
| /// as false in JS. |
| bool get isFalsey; |
| |
| static const jsBoolean = const JSBoolean(); |
| static const jsNumber = const JSNumber(); |
| static const jsNull = const JSNull(); |
| static const jsObject = const JSObject(); |
| static const jsString = const JSString(); |
| static const jsUnknown = const JSUnknown(); |
| } |
| |
| /// Inhabited by booleans (including JSBool), null, and undefined |
| class JSBoolean extends JSType { |
| const JSBoolean(); |
| bool get isPrimitive => true; |
| bool get isPrimitiveInJS => true; |
| bool get isFalsey => true; |
| } |
| |
| /// Inhabited by numbers, null, and undefined |
| /// In practice, this is 4 types: num, int, double, and _interceptors.JSNumber. |
| /// |
| /// _interceptors.JSNumber is the type that actually "implements" all numbers, |
| /// hence it's a subtype of int and double (and num). |
| /// It's defined in our "dart:_interceptors". |
| class JSNumber extends JSType { |
| const JSNumber(); |
| bool get isPrimitive => true; |
| bool get isPrimitiveInJS => true; |
| bool get isFalsey => true; |
| } |
| |
| /// Inhabited by null and undefined |
| class JSNull extends JSType { |
| const JSNull(); |
| bool get isPrimitive => false; |
| bool get isPrimitiveInJS => false; |
| bool get isFalsey => true; |
| } |
| |
| /// Inhabited by objects, null, and undefined |
| class JSObject extends JSType { |
| const JSObject(); |
| bool get isPrimitive => false; |
| bool get isPrimitiveInJS => false; |
| bool get isFalsey => false; |
| } |
| |
| /// Inhabited by strings (including JSString), null, and undefined |
| class JSString extends JSType { |
| const JSString(); |
| bool get isPrimitive => true; |
| bool get isPrimitiveInJS => false; |
| bool get isFalsey => true; |
| } |
| |
| /// Inhabitance not statically known |
| class JSUnknown extends JSType { |
| const JSUnknown(); |
| bool get isPrimitive => false; |
| bool get isPrimitiveInJS => false; |
| bool get isFalsey => true; |
| } |
| |
| class JSTypeRep { |
| final StrongTypeSystemImpl rules; |
| final TypeProvider types; |
| |
| JSTypeRep(this.rules, this.types); |
| |
| JSType typeFor(DartType type) { |
| while (type is TypeParameterType) { |
| type = (type as TypeParameterType).element.bound; |
| } |
| if (type == null) return JSType.jsUnknown; |
| if (type.isDartCoreNull) return JSType.jsNull; |
| // Note that this should be changed if Dart gets non-nullable types |
| if (type.isBottom) return JSType.jsNull; |
| if (rules.isSubtypeOf(type, types.numType)) return JSType.jsNumber; |
| if (rules.isSubtypeOf(type, types.boolType)) return JSType.jsBoolean; |
| if (rules.isSubtypeOf(type, types.stringType)) return JSType.jsString; |
| if (type.isDartAsyncFutureOr) { |
| var argument = (type as InterfaceType).typeArguments[0]; |
| var argumentRep = typeFor(argument); |
| if (argumentRep is JSObject || argumentRep is JSNull) { |
| return JSType.jsObject; |
| } |
| return JSType.jsUnknown; |
| } |
| if (type.isDynamic || type.isObject || type.isVoid) return JSType.jsUnknown; |
| return JSType.jsObject; |
| } |
| |
| /// If the type [t] is [int] or [double], or a type parameter |
| /// bounded by [int], [double] or [num] returns [num]. |
| /// Otherwise returns [t]. |
| DartType canonicalizeNumTypes(DartType t) => isNumber(t) ? types.numType : t; |
| |
| bool isNumber(DartType type) => typeFor(type) is JSNumber; |
| |
| /// Is this type known to be represented as Object or Null in JS. |
| bool isObjectOrNull(DartType t) { |
| var rep = typeFor(t); |
| return rep is JSObject || rep is JSNull; |
| } |
| |
| bool isUnknown(DartType t) => typeFor(t) is JSUnknown; |
| |
| bool isPrimitive(DartType t) => typeFor(t).isPrimitive; |
| |
| bool isPrimitiveInJS(DartType t) => typeFor(t).isPrimitiveInJS; |
| |
| bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => |
| isPrimitiveInJS(leftT) && isPrimitiveInJS(rightT); |
| |
| bool unaryOperationIsPrimitive(DartType t) => isPrimitiveInJS(t); |
| |
| /// True if the JS double equals (`==`) comparison on the representation |
| /// of these two types could potentially cause a conversion. |
| bool equalityMayConvert(DartType t0, DartType t1) => !equalOrNull(t0, t1); |
| |
| // Are t0 and t1 known to either be represented by the same type |
| // or else one or both of them is represented by Null |
| bool equalOrNull(DartType t0, DartType t1) { |
| var rep0 = typeFor(t0); |
| var rep1 = typeFor(t1); |
| if (rep0 is JSNull || rep1 is JSNull) return true; |
| return rep0 == rep1 && rep0 is! JSUnknown; |
| } |
| } |