Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(343)

Unified Diff: lib/declaration.ts

Issue 2394683003: JS Interop Facade generation polish.
Patch Set: more cleanup Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/base.ts ('k') | lib/facade_converter.ts » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/declaration.ts
diff --git a/lib/declaration.ts b/lib/declaration.ts
index 68bef4461317208b5688f5a1e4bddff8c07e549a..9f0df8eb189fd8d1bf5d49bbcd9739bf0ff4322a 100644
--- a/lib/declaration.ts
+++ b/lib/declaration.ts
@@ -3,11 +3,15 @@ import * as ts from 'typescript';
import * as base from './base';
import {FacadeConverter} from './facade_converter';
import {Transpiler} from './main';
-import {MergedParameter, MergedType} from './merge';
+import {MergedParameter, MergedType, MergedTypeParameters} from './merge';
export function isFunctionLikeProperty(
decl: ts.PropertyDeclaration|ts.ParameterDeclaration, tc: ts.TypeChecker): boolean {
if (!decl.type) return false;
+ if (decl.name.kind !== ts.SyntaxKind.Identifier) {
+ // No need to promote properties
+ return false;
+ }
let name = base.ident(decl.name);
if (name.match(/^on[A-Z]/)) return false;
return base.isFunctionType(decl.type, tc);
@@ -16,7 +20,6 @@ export function isFunctionLikeProperty(
export default class DeclarationTranspiler extends base.TranspilerBase {
private tc: ts.TypeChecker;
- private moduleStack: string[] = [];
private extendsClass: boolean = false;
static NUM_FAKE_REST_PARAMETERS = 5;
@@ -24,19 +27,40 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; }
setFacadeConverter(fc: FacadeConverter) { this.fc = fc; }
- getJsPath(node: ts.Node): string {
- let path = [].concat(this.moduleStack);
+ getJsPath(node: ts.Node, suppressUnneededPaths = true): string {
+ let path: Array<String> = [];
+ let moduleDecl =
+ base.getAncestor(node, ts.SyntaxKind.ModuleDeclaration) as ts.ModuleDeclaration;
+ while (moduleDecl != null) {
+ path.unshift(moduleDecl.name.text);
+ moduleDecl =
+ base.getAncestor(
+ moduleDecl.parent, ts.SyntaxKind.ModuleDeclaration) as ts.ModuleDeclaration;
+ }
+
let classDecl = base.getEnclosingClass(node);
if (classDecl) {
- path.push(classDecl.name.text);
+ if (classDecl.kind === ts.SyntaxKind.InterfaceDeclaration) {
+ let interfaceDecl = classDecl as base.ExtendedInterfaceDeclaration;
+ if (interfaceDecl.classLikeVariableDeclaration) {
+ // We upgrade these variable interface declarations to behave more
+ // like class declarations as we have a valid concrete JS class to
+ // an appropriate class object.
+ return this.getJsPath(interfaceDecl.classLikeVariableDeclaration, false);
+ }
+ return '';
+ } else {
+ path.push(classDecl.name.text);
+ }
}
switch (node.kind) {
case ts.SyntaxKind.ModuleDeclaration:
+ path.push((<ts.ModuleDeclaration>node).name.text);
break;
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.InterfaceDeclaration:
- path.push((<base.ClassLike>node).name.text);
+ // Already handled by call to getEnclosingClass.
break;
case ts.SyntaxKind.EnumDeclaration:
path.push((<ts.EnumDeclaration>node).name.text);
@@ -44,6 +68,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.VariableDeclaration:
case ts.SyntaxKind.MethodDeclaration:
+ case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.SetAccessor:
@@ -55,8 +80,9 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
default:
throw 'Internal error. Unexpected node kind:' + node.kind;
}
- if (path.length === 1) {
- // No need to specify the path if is simply the node name.
+ if (suppressUnneededPaths && path.length === 1) {
+ // No need to specify the path if is simply the node name or the escaped version of the node
+ // name.
return '';
}
return path.join('.');
@@ -64,20 +90,10 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
private isAnonymousInterface(node: ts.Node): boolean {
if (node.kind !== ts.SyntaxKind.InterfaceDeclaration) return false;
- // This is a bit of a hack but for the purposes of Dart codegen,
- // interfaces with static members or constructors have a known class name
- // at least for the purposes of resolving static members.
- // Example case that triggers this case:
- // interface Foo {
- // bar();
- // }
- // declare let Foo: {
- // new(): Foo,
- // SOME_STATIC : number;
- // }
- return (<ts.InterfaceDeclaration>node).members.every((m: ts.Declaration) => {
- return m.kind !== ts.SyntaxKind.Constructor && !base.isStatic(m);
- });
+ let interfaceDecl = node as base.ExtendedInterfaceDeclaration;
+ // If we were able to associate a variable declaration with the interface definition then
+ // the interface isn't actually anonymous.
+ return !interfaceDecl.classLikeVariableDeclaration;
}
maybeEmitJsAnnotation(node: ts.Node) {
@@ -101,12 +117,12 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
/**
* Emit fake constructors to placate the Dart Analyzer for JS Interop classes.
*/
- maybeEmitFakeConstructors(decl: base.ClassLike) {
+ maybeEmitFakeConstructors(decl: ts.Node) {
if (decl.kind === ts.SyntaxKind.ClassDeclaration) {
// Required to avoid spurious dart errors involving base classes without
// default constructors.
this.emit('// @Ignore\n');
- this.fc.visitTypeName(decl.name);
+ this.fc.visitTypeName((<ts.ClassDeclaration>decl).name);
this.emit('.fakeConstructor$()');
if (this.extendsClass) {
// Required to keep the Dart Analyzer happy when a class has subclasses.
@@ -126,7 +142,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
if (name.kind !== ts.SyntaxKind.Identifier) {
throw 'Internal error: unexpected function name kind:' + name.kind;
}
- let entry = this.fc.lookupCustomDartTypeName(<ts.Identifier>name, this.insideCodeComment);
+ let entry = this.fc.lookupCustomDartTypeName(<ts.Identifier>name);
if (entry) {
this.emit(entry.name);
return;
@@ -167,7 +183,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
boolean {
let type = <ts.InterfaceType>this.tc.getTypeAtLocation(decl);
- let properties = this.tc.getPropertiesOfType(type);
+ let symbols = this.tc.getPropertiesOfType(type);
let baseTypes = this.tc.getBaseTypes(type);
if (this.notSimpleBagOfProperties(type)) return false;
for (let i = 0; i < baseTypes.length; ++i) {
@@ -175,9 +191,19 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
if (this.notSimpleBagOfProperties(baseType)) return false;
}
+ let properties: ts.Declaration[] = [];
+
+ for (let i = 0; i < symbols.length; ++i) {
+ let symbol = symbols[i];
+ let property = symbol.valueDeclaration;
+ properties.push(property);
+ }
+ return this.hasOnlyPropertiesHelper(properties, outProperties);
+ }
+
+ hasOnlyPropertiesHelper(properties: ts.Declaration[], outProperties: ts.Declaration[]): boolean {
for (let i = 0; i < properties.length; ++i) {
- let symbol = properties[i];
- let node = symbol.valueDeclaration;
+ let node = properties[i];
switch (node.kind) {
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.PropertySignature:
@@ -195,15 +221,19 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
return outProperties.length > 0;
}
- visitClassBody(decl: base.ClassLike) {
+ visitClassBody(decl: base.ClassLike|ts.TypeLiteralNode, name: ts.Identifier) {
let properties: ts.PropertyDeclaration[] = [];
- let isPropertyBag = decl.kind === ts.SyntaxKind.InterfaceDeclaration &&
- this.hasOnlyProperties(<ts.InterfaceDeclaration>decl, properties);
+ let isPropertyBag = false;
+ if (decl.kind === ts.SyntaxKind.InterfaceDeclaration) {
+ isPropertyBag = this.hasOnlyProperties(<ts.InterfaceDeclaration>decl, properties);
+ } else if (decl.kind === ts.SyntaxKind.TypeLiteral) {
+ isPropertyBag = this.hasOnlyPropertiesHelper(decl.members, properties);
+ }
this.visitMergingOverloads(decl.members);
if (isPropertyBag) {
this.emit('external factory');
- this.fc.visitTypeName(decl.name);
+ this.fc.visitTypeName(name);
this.emitNoSpace('({');
for (let i = 0; i < properties.length; i++) {
if (i > 0) this.emitNoSpace(',');
@@ -274,18 +304,28 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.InterfaceDeclaration:
case ts.SyntaxKind.VariableStatement:
- orderedGroups.push([node]);
- return;
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.SetAccessor:
case ts.SyntaxKind.SemicolonClassElement:
case ts.SyntaxKind.ModuleDeclaration:
case ts.SyntaxKind.TypeAliasDeclaration:
case ts.SyntaxKind.ExportAssignment:
+ case ts.SyntaxKind.EnumDeclaration:
+ case ts.SyntaxKind.ImportDeclaration:
+ case ts.SyntaxKind.ExportDeclaration:
+ case ts.SyntaxKind.GlobalModuleExportDeclaration:
+ case ts.SyntaxKind.ImportEqualsDeclaration:
+ case ts.SyntaxKind.EmptyStatement:
+ case ts.SyntaxKind.ExpressionStatement:
+ // Types where we don't need to perform any merging overloads work.
orderedGroups.push([node]);
return;
default:
- console.log('Warning: unexpected type... overloads: ' + node.kind + ' ' + node.getText());
+ // This warning is to make sure we aren't quietly missing a type we need to perform
+ // overload chunking and merging on.
+ console.log(
+ 'Warning: unexpected type found looking for overloads: ' + node.kind + ' ' +
+ node.getText());
orderedGroups.push([node]);
return;
}
@@ -344,9 +384,13 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
let mergedType = new MergedType(this.fc);
mergedType.merge(first.type);
+ let mergedTypeParams = new MergedTypeParameters(this.fc);
+ mergedTypeParams.merge(first.typeParameters);
+
for (let i = 1; i < group.length; ++i) {
let signature = <ts.SignatureDeclaration>group[i];
mergedType.merge(signature.type);
+ mergedTypeParams.merge(signature.typeParameters);
let overlap = Math.min(signature.parameters.length, mergedParams.length);
for (let j = 0; j < overlap; ++j) {
mergedParams[j].merge(signature.parameters[j]);
@@ -363,6 +407,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
merged.parameters = <ts.NodeArray<ts.ParameterDeclaration>>mergedParams.map(
(p) => p.toParameterDeclaration());
merged.type = mergedType.toTypeNode();
+ merged.typeParameters = mergedTypeParams.toTypeParameters();
this.fc.visit(merged);
});
@@ -379,12 +424,15 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
switch (node.kind) {
case ts.SyntaxKind.ModuleDeclaration:
let moduleDecl = <ts.ModuleDeclaration>node;
+ if (moduleDecl.name.text.slice(0, 2) === '..') {
+ this.emit(
+ '\n// Library augmentation not allowed by Dart. Ignoring augmentation of ' +
+ moduleDecl.name.text + '\n');
+ break;
+ }
this.emit('\n// Module ' + moduleDecl.name.text + '\n');
- this.moduleStack.push(moduleDecl.name.text);
-
this.visit(moduleDecl.body);
this.emit('\n// End module ' + moduleDecl.name.text + '\n');
- this.moduleStack.pop();
break;
case ts.SyntaxKind.ExportKeyword:
// TODO(jacobr): perhaps add a specific Dart annotation to indicate
@@ -439,7 +487,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
let arrayType = <ts.ArrayTypeNode>type;
paramType = arrayType.elementType;
} else if (type.kind !== ts.SyntaxKind.AnyKeyword) {
- throw 'Unexpected type for varargs: ' + type.kind;
+ console.log('Warning: falling back to dynamic for varArgs type: ' + type.getText());
}
}
@@ -477,6 +525,10 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
let member = <ts.EnumMember>node;
this.visit(member.name);
} break;
+ case ts.SyntaxKind.SourceFile:
+ let sourceFile = node as ts.SourceFile;
+ this.visitMergingOverloads(sourceFile.statements);
+ break;
case ts.SyntaxKind.ModuleBlock: {
let block = <ts.ModuleBlock>node;
this.visitMergingOverloads(block.statements);
@@ -530,11 +582,27 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
// let exportAssignment = <ts.ExportAssignment>node;
this.emit('/* WARNING: export assignment not yet supported. */\n');
break;
- case ts.SyntaxKind.TypeAliasDeclaration:
- // Dart does not provide special syntax for definning type alais
- // declarations so we do not emit anything here and resolve alaises
- // to their original types at each usage site.
- break;
+ case ts.SyntaxKind.TypeAliasDeclaration: {
+ // Object literal type alias declarations are equivalent to interface declarations.
+ let alias = <ts.TypeAliasDeclaration>node;
+ let type = alias.type;
+ if (type.kind === ts.SyntaxKind.TypeLiteral) {
+ let literal = <ts.TypeLiteralNode>type;
+ this.emit('@anonymous\n@JS()\n');
+ this.visitClassLikeHelper(
+ 'abstract class', literal, alias.name, alias.typeParameters, null);
+ } else if (type.kind === ts.SyntaxKind.FunctionType) {
+ // Function type alias definitions are equivalent to dart typedefs.
+ this.visitFunctionTypedefInterface(
+ base.ident(alias.name), type as ts.FunctionTypeNode, alias.typeParameters);
+ } else {
+ this.enterCodeComment();
+ this.emit(alias.getText());
+ this.exitCodeComment();
+ this.emit('\n');
+ }
+ // We ignore other type alias declarations as Dart doesn't have a corresponding feature yet.
+ } break;
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.InterfaceDeclaration: {
this.extendsClass = false;
@@ -547,7 +615,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
break;
}
- let customName = this.fc.lookupCustomDartTypeName(classDecl.name, this.insideCodeComment);
+ let customName = this.fc.lookupCustomDartTypeName(classDecl.name);
if (customName && !customName.keep) {
this.emit('\n/* Skipping class ' + base.ident(classDecl.name) + '*/\n');
break;
@@ -586,10 +654,19 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
// Find containing class name.
let classDecl = base.getEnclosingClass(ctorDecl);
if (!classDecl) this.reportError(ctorDecl, 'cannot find outer class node');
+ let isAnonymous = this.isAnonymousInterface(classDecl);
+ if (isAnonymous) {
+ this.emit('// Constructors on anonymous interfaces are not yet supported.\n');
+ this.enterCodeComment();
+ }
this.visitDeclarationMetadata(ctorDecl);
this.fc.visitTypeName(classDecl.name);
this.visitParameters(ctorDecl.parameters);
this.emitNoSpace(';');
+ if (isAnonymous) {
+ this.exitCodeComment();
+ this.emit('\n');
+ }
} break;
case ts.SyntaxKind.PropertyDeclaration:
this.visitProperty(<ts.PropertyDeclaration>node);
@@ -684,11 +761,9 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
this.enterCodeComment();
}
this.emitNoSpace('<');
- // Emit the names literally instead of visiting, otherwise they will be replaced with the
- // comment hack themselves.
- // TODO(jacobr): we can use the regular type parameter visiting pattern
- // now that we properly track whether we are inside a comment.
- this.emitNoSpace(fn.typeParameters.map(p => base.ident(p.name)).join(', '));
+ this.enterTypeArguments();
+ this.visitList(fn.typeParameters);
+ this.exitTypeArguments();
this.emitNoSpace('>');
if (!insideComment) {
this.exitCodeComment();
@@ -738,15 +813,25 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
}
private visitClassLike(keyword: string, decl: base.ClassLike) {
+ return this.visitClassLikeHelper(
+ keyword, decl, decl.name, decl.typeParameters, decl.heritageClauses);
+ }
+
+ private visitClassLikeHelper(
+ keyword: string, decl: base.ClassLike|ts.TypeLiteralNode, name: ts.Identifier,
+ typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>,
+ heritageClauses: ts.NodeArray<ts.HeritageClause>) {
this.emit(keyword);
- this.fc.visitTypeName(decl.name);
- if (decl.typeParameters) {
+ this.fc.visitTypeName(name);
+ if (typeParameters) {
this.emit('<');
- this.visitList(decl.typeParameters);
+ this.enterTypeArguments();
+ this.visitList(typeParameters);
+ this.exitTypeArguments();
this.emit('>');
}
- this.visitEachIfPresent(decl.heritageClauses);
+ this.visitEachIfPresent(heritageClauses);
this.emit('{');
this.maybeEmitFakeConstructors(decl);
@@ -766,7 +851,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
(ctor) =>
(<ts.ConstructorDeclaration>ctor).parameters.forEach(synthesizePropertyParam));
- this.visitClassBody(decl);
+ this.visitClassBody(decl, name);
this.emit('}');
}
@@ -801,7 +886,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
* call signature, by translating to a Dart `typedef`.
*/
private visitFunctionTypedefInterface(
- name: string, signature: ts.CallSignatureDeclaration,
+ name: string, signature: ts.SignatureDeclaration,
typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>) {
this.emit('typedef');
if (signature.type) {
@@ -810,7 +895,9 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
this.emit(name);
if (typeParameters) {
this.emitNoSpace('<');
+ this.enterTypeArguments();
this.visitList(typeParameters);
+ this.exitTypeArguments();
this.emitNoSpace('>');
}
this.visitParameters(signature.parameters);
« no previous file with comments | « lib/base.ts ('k') | lib/facade_converter.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698