blob: bc29be0d9133f9688e2206b66f99a587f4093907 [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.
/// Poor-man's representation of a Closure type.
/// See https://developers.google.com/closure/compiler/docs/js-for-compiler
///
/// The goal here is not to completely support Closure's type system, but to
/// be able to generate just the types needed for DDC's JS output.
///
/// TODO(ochafik): Consider convergence with TypeScript, which has no nullability-awareness
/// (see http://www.typescriptlang.org/Handbook).
class ClosureType {
static const ClosureType _ALL = ClosureType._("*");
static const ClosureType _UNKNOWN = ClosureType._("?");
final String _representation;
final bool isNullable;
const ClosureType._(this._representation, {this.isNullable = true});
bool get isAll => _representation == "*";
bool get isUnknown => _representation == "?";
@override
toString() => _representation;
factory ClosureType.all() => _ALL;
factory ClosureType.unknown() => _UNKNOWN;
factory ClosureType.record(Map<String, ClosureType> fieldTypes) {
var entries = <String>[];
fieldTypes.forEach((n, t) => entries.add('$n: $t'));
return ClosureType._('{${entries.join(', ')}}');
}
factory ClosureType.function(
[List<ClosureType> paramTypes, ClosureType returnType]) {
if (paramTypes == null && returnType == null) {
return ClosureType.type("Function");
}
var suffix = returnType == null ? '' : ':$returnType';
return ClosureType._(
'function(${paramTypes == null ? '...*' : paramTypes.join(', ')})$suffix');
}
factory ClosureType.map([ClosureType keyType, ClosureType valueType]) =>
ClosureType._("Object<${keyType ?? _ALL}, ${valueType ?? _ALL}>");
factory ClosureType.type([String className = "Object"]) =>
ClosureType._(className);
factory ClosureType.array([ClosureType componentType]) =>
ClosureType._("Array<${componentType ?? _ALL}>");
factory ClosureType.undefined() =>
ClosureType._("undefined", isNullable: false);
factory ClosureType.number() => ClosureType._("number", isNullable: false);
factory ClosureType.boolean() => ClosureType._("boolean", isNullable: false);
factory ClosureType.string() => ClosureType._("string");
ClosureType toOptional() => ClosureType._("$this=");
ClosureType toNullable() => isNullable
? this
: ClosureType._(
_representation.startsWith('!')
? _representation.substring(1)
: "?$this",
isNullable: true);
ClosureType toNonNullable() => !isNullable
? this
: ClosureType._(
_representation.startsWith('?')
? _representation.substring(1)
: "!$this",
isNullable: false);
/// TODO(ochafik): See which optimizations make sense here (it could be that `(*|undefined)`
/// cannot be optimized to `*` when used to model optional record fields).
ClosureType or(ClosureType other) => ClosureType._("($this|$other)",
isNullable: isNullable || other.isNullable);
ClosureType orUndefined() => or(ClosureType.undefined());
}