Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Output Closure annotations when --closure #286

Merged
merged 1 commit into from
Sep 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/runtime/_operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ dart_library.library('dart_runtime/_operations', null, /* Imports */[
let names = getOwnPropertyNames(opts);
// Type is something other than a map
if (names.length == 0) return false;
for (name of names) {
for (var name of names) {
if (!(hasOwnProperty.call(type.named, name))) {
return false;
}
Expand Down
111 changes: 111 additions & 0 deletions lib/src/closure/closure_annotation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// 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.

library dev_compiler.src.closure.closure_annotation;

import 'closure_type.dart';

/// Set of closure annotations that can be [toString]ed to a single JsDoc comment.
/// See https://developers.google.com/closure/compiler/docs/js-for-compiler
///
/// TODO(ochafik): Support inclusion of 'normal' comments (including @param comments).
class ClosureAnnotation {
final bool isConst;
final bool isConstructor;
final bool isFinal;
final bool isNoCollapse;
final bool isNoSideEffects;
final bool isOverride;
final bool isPrivate;
final bool isProtected;
final bool isStruct;
final bool isTypedef;
final ClosureType lendsToType;
final ClosureType returnType;
final ClosureType superType;
final ClosureType thisType;
final ClosureType throwsType;
final ClosureType type;
final List<ClosureType> interfaces;
final List<String> templates;
final Map<String, ClosureType> paramTypes;

ClosureAnnotation({this.interfaces: const [], this.isConst: false,
this.isConstructor: false, this.isFinal: false, this.isNoCollapse: false,
this.isNoSideEffects: false, this.isOverride: false,
this.isPrivate: false, this.isProtected: false, this.isStruct: false,
this.isTypedef: false, this.lendsToType, this.paramTypes: const {},
this.returnType, this.superType, this.templates: const [], this.thisType,
this.throwsType, this.type});

@override
int get hashCode => _cachedString.hashCode;

@override
bool operator ==(other) =>
other is ClosureAnnotation && _cachedString == other._cachedString;

@override
String toString([String indent = '']) =>
_cachedString.replaceAll('\n', '\n$indent');

String __cachedString;
String get _cachedString {
if (__cachedString == null) {
bool isNonWildcard(ClosureType t) =>
t != null && !t.isAll && !t.isUnknown;

var lines = <String>[];
if (templates != null && templates.isNotEmpty) {
lines.add('@template ${templates.join(', ')}');
}
if (thisType != null) lines.add('@this {$thisType}');
if (isOverride) lines.add('@override');
if (isNoSideEffects) lines.add('@nosideeffects');
if (isNoCollapse) lines.add('@nocollapse');
if (lendsToType != null) lines.add('@lends {$lendsToType}');

{
var typeHolders = <String>[];
if (isPrivate) typeHolders.add('@private');
if (isProtected) typeHolders.add('@protected');
if (isFinal) typeHolders.add('@final');
if (isConst) typeHolders.add('@const');
if (isTypedef) typeHolders.add('@typedef');
if (isNonWildcard(type)) {
if (typeHolders.isEmpty) typeHolders.add('@type');
typeHolders.add('{$type}');
}
if (!typeHolders.isEmpty) lines.add(typeHolders.join(' '));
}

{
List constructorLine = [];
if (isConstructor) constructorLine.add('@constructor');
if (isStruct) constructorLine.add('@struct');
if (isNonWildcard(superType)) {
constructorLine.add('@extends {$superType}');
}

if (constructorLine.isNotEmpty) lines.add(constructorLine.join(' '));
}

for (var interface in interfaces) {
if (isNonWildcard(interface)) lines.add('@implements {$interface}');
}

paramTypes.forEach((String paramName, ClosureType paramType) {
// Must output params even with wildcard type.
lines.add('@param {$paramType} $paramName');
});
if (isNonWildcard(returnType)) lines.add('@return {$returnType}');
if (isNonWildcard(throwsType)) lines.add('@throws {$throwsType}');

if (lines.length == 0) return '';
if (lines.length == 1) return '/** ${lines.single} */';
__cachedString = '/**\n' + lines.map((l) => ' * $l').join('\n') + '\n */';
}
return __cachedString;
}
}
140 changes: 140 additions & 0 deletions lib/src/closure/closure_annotator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// 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.

library dev_compiler.src.closure.closure_codegen;

import 'package:analyzer/analyzer.dart' show ParameterKind;
import 'package:analyzer/src/generated/element.dart';

import 'closure_annotation.dart';
import 'closure_type.dart';
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;

/// Mixin that can generate [ClosureAnnotation]s for Dart elements and types.
abstract class ClosureAnnotator {
TypeProvider get types;

/// Must return a JavaScript qualified name that can be used to refer to [type].
String getQualifiedName(TypeDefiningElement type);

ClosureAnnotation closureAnnotationForVariable(VariableElement e) =>
new ClosureAnnotation(type: _closureTypeForDartType(e.type),
// Note: we don't set isConst here because Closure's constness and
// Dart's are not really compatible.
isFinal: e.isFinal || e.isConst);

/// We don't use Closure's `@typedef` annotations
ClosureAnnotation closureAnnotationForTypeDef(FunctionTypeAliasElement e) =>
new ClosureAnnotation(
type: _closureTypeForDartType(e.type, forceTypeDefExpansion: true),
isTypedef: true);

ClosureAnnotation closureAnnotationForDefaultConstructor(ClassElement e) =>
new ClosureAnnotation(
superType: _closureTypeForDartType(e.supertype),
interfaces: e.interfaces.map(_closureTypeForDartType).toList());

ClosureAnnotation closureAnnotationFor(
ExecutableElement e, String namedArgsMapName) {
var paramTypes = <String, ClosureType>{};
var namedArgs = <String, ClosureType>{};
for (var param in e.parameters) {
var t = _closureTypeForDartType(param.type);
switch (param.parameterKind) {
case ParameterKind.NAMED:
namedArgs[param.name] = t.orUndefined();
break;
case ParameterKind.POSITIONAL:
paramTypes[param.name] = t.toOptional();
break;
case ParameterKind.REQUIRED:
paramTypes[param.name] = t;
break;
}
}
if (namedArgs.isNotEmpty) {
paramTypes[namedArgsMapName] =
new ClosureType.record(namedArgs).toOptional();
}

var returnType = e is ConstructorElement
? (e.isFactory ? _closureTypeForClass(e.enclosingElement) : null)
: _closureTypeForDartType(e.returnType);

return new ClosureAnnotation(isOverride: e.isOverride,
// Note: Dart and Closure privacy are not compatible: don't set `isPrivate: e.isPrivate`.
paramTypes: paramTypes, returnType: returnType);
}

Map<DartType, ClosureType> __commonTypes;
Map<DartType, ClosureType> get _commonTypes {
if (__commonTypes == null) {
var numberType = new ClosureType.number().toNullable();
__commonTypes = {
types.intType: numberType,
types.numType: numberType,
types.doubleType: numberType,
types.boolType: new ClosureType.boolean().toNullable(),
types.stringType: new ClosureType.string(),
};
}
return __commonTypes;
}

ClosureType _closureTypeForClass(ClassElement classElement, [DartType type]) {
ClosureType closureType = _commonTypes[type];
if (closureType != null) return closureType;

var fullName = _getFullName(classElement);
switch (fullName) {
// TODO(ochafik): Test DartTypes directly if possible.
case "dart.js.JsArray":
return new ClosureType.array(
type is InterfaceType && type.typeArguments.length == 1
? _closureTypeForDartType(type.typeArguments.single)
: null);
case "dart.js.JsObject":
return new ClosureType.map();
case "dart.js.JsFunction":
return new ClosureType.function();
default:
return new ClosureType.type(getQualifiedName(classElement));
}
}

ClosureType _closureTypeForDartType(DartType type,
{bool forceTypeDefExpansion: false}) {
if (type == null || type.isBottom || type.isDynamic) {
return new ClosureType.unknown();
}
if (type.isVoid) return null;
if (type is FunctionType) {
if (!forceTypeDefExpansion && type.element.name != '') {
return new ClosureType.type(getQualifiedName(type.element));
}

var args = []
..addAll(type.normalParameterTypes.map(_closureTypeForDartType))
..addAll(type.optionalParameterTypes
.map((t) => _closureTypeForDartType(t).toOptional()));
if (type.namedParameterTypes.isNotEmpty) {
var namedArgs = <String, ClosureType>{};
type.namedParameterTypes.forEach((n, t) {
namedArgs[n] = _closureTypeForDartType(t).orUndefined();
});
args.add(new ClosureType.record(namedArgs).toOptional());
}
return new ClosureType.function(
args, _closureTypeForDartType(type.returnType));
}
if (type is InterfaceType) {
return _closureTypeForClass(type.element, type);
}
return new ClosureType.unknown();
}

/// TODO(ochafik): Use a package-and-file-uri-dependent naming, since libraries can collide.
String _getFullName(ClassElement type) =>
type.library.name == '' ? type.name : '${type.library.name}.${type.name}';
}
80 changes: 80 additions & 0 deletions lib/src/closure/closure_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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.

library dev_compiler.src.closure.closure_type;

/// 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 = const ClosureType._("*");
static const ClosureType _UNKNOWN = const 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 new ClosureType._('{${entries.join(', ')}}');
}
factory ClosureType.function([List<ClosureType> paramTypes, ClosureType returnType]) {
if (paramTypes == null && returnType == null) {
return new ClosureType.type("Function");
}
var suffix = returnType == null ? '' : ':$returnType';
return new ClosureType._(
'function(${paramTypes == null ? '...*' : paramTypes.join(', ')})$suffix');
}

factory ClosureType.map([ClosureType keyType, ClosureType valueType]) =>
new ClosureType._("Object<${keyType ?? _ALL}, ${valueType ?? _ALL}>");

factory ClosureType.type([String className = "Object"]) =>
new ClosureType._(className);

factory ClosureType.array([ClosureType componentType]) =>
new ClosureType._("Array<${componentType ?? _ALL}>");

factory ClosureType.undefined() => new ClosureType._("undefined", isNullable: false);
factory ClosureType.number() => new ClosureType._("number", isNullable: false);
factory ClosureType.boolean() => new ClosureType._("boolean", isNullable: false);
factory ClosureType.string() => new ClosureType._("string");

ClosureType toOptional() =>
new ClosureType._("$this=");

ClosureType toNullable() =>
isNullable ? this : new ClosureType._(
_representation.startsWith('!') ? _representation.substring(1) : "?$this",
isNullable: true);

ClosureType toNonNullable() =>
!isNullable ? this : new 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) =>
new ClosureType._("($this|$other)", isNullable: isNullable || other.isNullable);

ClosureType orUndefined() =>
or(new ClosureType.undefined());
}
Loading