| OLD | NEW |
| 1 import ts = require('typescript'); | 1 import ts = require('typescript'); |
| 2 import base = require('./base'); | 2 import base = require('./base'); |
| 3 import {FacadeConverter} from './facade_converter'; | 3 import {FacadeConverter} from './facade_converter'; |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * To support arbitrary d.ts files in Dart we often have to merge two TypeScript | 6 * To support arbitrary d.ts files in Dart we often have to merge two TypeScript |
| 7 * types into a single Dart type because Dart lacks features such as method | 7 * types into a single Dart type because Dart lacks features such as method |
| 8 * overloads, type aliases, and union types. | 8 * overloads, type aliases, and union types. |
| 9 */ | 9 */ |
| 10 export class MergedType { | 10 export class MergedType { |
| 11 constructor(private fc: FacadeConverter) {} | 11 constructor(private fc: FacadeConverter) {} |
| 12 | 12 |
| 13 merge(t?: ts.TypeNode) { | 13 merge(t?: ts.TypeNode) { |
| 14 if (t) { | 14 if (t) { |
| 15 // TODO(jacobr): get a better unique name for a type. | 15 // TODO(jacobr): get a better unique name for a type. |
| 16 switch (t.kind) { | 16 switch (t.kind) { |
| 17 case ts.SyntaxKind.NullKeyword: |
| 18 // No need to include the null type as all Dart types are nullable any
way. |
| 19 return; |
| 17 case ts.SyntaxKind.UnionType: | 20 case ts.SyntaxKind.UnionType: |
| 18 let union = <ts.UnionTypeNode>t; | 21 let union = <ts.UnionTypeNode>t; |
| 19 union.types.forEach(this.merge.bind(this)); | 22 union.types.forEach(this.merge.bind(this)); |
| 20 return; | 23 return; |
| 21 case ts.SyntaxKind.LastTypeNode: | 24 case ts.SyntaxKind.IntersectionType: |
| 22 this.merge((t as ts.ParenthesizedTypeNode).type); | 25 // Arbitrarily pick the first type of the intersection type as the mer
ged type. |
| 26 // TODO(jacobr): re-evaluate this logic. |
| 27 let intersection = <ts.IntersectionTypeNode>t; |
| 28 this.merge(intersection.types[0]); |
| 23 return; | 29 return; |
| 24 case ts.SyntaxKind.TypePredicate: | 30 case ts.SyntaxKind.TypePredicate: |
| 25 this.merge((t as ts.TypePredicateNode).type); | 31 this.merge((t as ts.TypePredicateNode).type); |
| 26 return; | 32 return; |
| 27 case ts.SyntaxKind.TypeReference: | 33 case ts.SyntaxKind.TypeReference: |
| 28 // We need to follow Alais types as Dart does not support them for non | 34 // We need to follow Alais types as Dart does not support them for non |
| 29 // function types. TODO(jacobr): handle them for Function types? | 35 // function types. TODO(jacobr): handle them for Function types? |
| 30 let typeRef = <ts.TypeReferenceNode>t; | 36 let typeRef = <ts.TypeReferenceNode>t; |
| 31 let decl = this.fc.getDeclaration(typeRef.typeName); | 37 let decl = this.fc.getDeclaration(typeRef.typeName); |
| 32 if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration)
{ | 38 if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration)
{ |
| 33 let alias = <ts.TypeAliasDeclaration>decl; | 39 let alias = <ts.TypeAliasDeclaration>decl; |
| 40 if (!base.supportedAliasType(alias)) { |
| 41 if (typeRef.typeArguments) { |
| 42 console.log( |
| 43 'Warning: typeReference with arguements not supported yet:'
+ t.getText()); |
| 44 } |
| 34 | 45 |
| 35 if (typeRef.typeArguments) { | 46 this.merge(alias.type); |
| 36 throw 'TypeReference with arguements not supported yet:' + t.getTe
xt(); | |
| 37 } | 47 } |
| 38 | |
| 39 this.merge(alias.type); | |
| 40 return; | 48 return; |
| 41 } | 49 } |
| 42 break; | 50 break; |
| 43 default: | 51 default: |
| 44 break; | 52 break; |
| 45 } | 53 } |
| 46 this.types[this.fc.generateDartTypeName(t, true)] = t; | 54 this.types[this.fc.generateDartTypeName(t, {insideComment: true})] = t; |
| 47 } | 55 } |
| 48 } | 56 } |
| 49 | 57 |
| 50 toTypeNode(): ts.TypeNode { | 58 toTypeNode(): ts.TypeNode { |
| 51 let names = Object.getOwnPropertyNames(this.types); | 59 let names = Object.getOwnPropertyNames(this.types); |
| 52 if (names.length === 0) { | 60 if (names.length === 0) { |
| 53 return null; | 61 return null; |
| 54 } | 62 } |
| 55 if (names.length === 1) { | 63 if (names.length === 1) { |
| 56 return this.types[names[0]]; | 64 return this.types[names[0]]; |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 102 | 110 |
| 103 setOptional() { this.optional = true; } | 111 setOptional() { this.optional = true; } |
| 104 | 112 |
| 105 private name: {[s: string]: boolean} = {}; | 113 private name: {[s: string]: boolean} = {}; |
| 106 private type: MergedType; | 114 private type: MergedType; |
| 107 private optional: boolean = false; | 115 private optional: boolean = false; |
| 108 private textRange: ts.TextRange; | 116 private textRange: ts.TextRange; |
| 109 } | 117 } |
| 110 | 118 |
| 111 /** | 119 /** |
| 120 * Handle a parameter that is the result of merging parameter declarations from |
| 121 * multiple method overloads. |
| 122 */ |
| 123 export class MergedTypeParameter { |
| 124 constructor(param: ts.TypeParameterDeclaration, fc: FacadeConverter) { |
| 125 this.constraint = new MergedType(fc); |
| 126 this.textRange = param; |
| 127 this.merge(param); |
| 128 this.name = base.ident(param.name); |
| 129 } |
| 130 |
| 131 merge(param: ts.TypeParameterDeclaration) { |
| 132 this.constraint.merge(param.constraint); |
| 133 // We ignore param.expression as it is not supported by Dart. |
| 134 } |
| 135 |
| 136 toTypeParameterDeclaration(): ts.TypeParameterDeclaration { |
| 137 let ret = <ts.TypeParameterDeclaration>ts.createNode(ts.SyntaxKind.TypeParam
eter); |
| 138 let nameIdentifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
| 139 nameIdentifier.text = this.name; |
| 140 ret.name = nameIdentifier; |
| 141 base.copyLocation(this.textRange, ret); |
| 142 let constraint = this.constraint.toTypeNode(); |
| 143 // TODO(jacobr): remove this check once we have support for union types with
in comments. |
| 144 // We can't currently handle union types in merged type parameters as the co
mments for type |
| 145 // parameters in function types are not there for documentation and impact s
trong mode. |
| 146 if (constraint && constraint.kind !== ts.SyntaxKind.UnionType) { |
| 147 ret.constraint = constraint; |
| 148 } |
| 149 return ret; |
| 150 } |
| 151 |
| 152 private name: string; |
| 153 private constraint: MergedType; |
| 154 private textRange: ts.TextRange; |
| 155 } |
| 156 |
| 157 /** |
| 158 * Handle a parameter that is the result of merging parameter declarations from |
| 159 * multiple method overloads. |
| 160 */ |
| 161 export class MergedTypeParameters { |
| 162 private mergedParameters: {[s: string]: MergedTypeParameter} = {}; |
| 163 private textRange: ts.TextRange; |
| 164 |
| 165 constructor(private fc: FacadeConverter) {} |
| 166 |
| 167 merge(params: ts.NodeArray<ts.TypeParameterDeclaration>) { |
| 168 if (!params) return; |
| 169 if (!this.textRange) { |
| 170 this.textRange = params; |
| 171 } |
| 172 for (let i = 0; i < params.length; i++) { |
| 173 let param = params[i]; |
| 174 let name = base.ident(param.name); |
| 175 if (Object.hasOwnProperty.call(this.mergedParameters, name)) { |
| 176 let merged = this.mergedParameters[name]; |
| 177 if (merged) { |
| 178 merged.merge(param); |
| 179 } |
| 180 } else { |
| 181 this.mergedParameters[name] = new MergedTypeParameter(param, this.fc); |
| 182 } |
| 183 } |
| 184 } |
| 185 |
| 186 toTypeParameters(): ts.NodeArray<ts.TypeParameterDeclaration> { |
| 187 let names = Object.getOwnPropertyNames(this.mergedParameters); |
| 188 if (names.length === 0) { |
| 189 return undefined; |
| 190 } |
| 191 |
| 192 let ret = [] as ts.NodeArray<ts.TypeParameterDeclaration>; |
| 193 base.copyLocation(this.textRange, ret); |
| 194 for (let i = 0; i < names.length; ++i) { |
| 195 ret.push(this.mergedParameters[names[i]].toTypeParameterDeclaration()); |
| 196 } |
| 197 return ret; |
| 198 } |
| 199 } |
| 200 |
| 201 /** |
| 112 * Normalize a SourceFile | 202 * Normalize a SourceFile |
| 113 */ | 203 */ |
| 114 export function normalizeSourceFile(f: ts.SourceFile) { | 204 export function normalizeSourceFile(f: ts.SourceFile, fc: FacadeConverter) { |
| 115 let modules: {[name: string]: ts.ModuleDeclaration} = {}; | 205 let modules: {[name: string]: ts.ModuleDeclaration} = {}; |
| 116 | 206 |
| 117 // Merge top level modules. | 207 // Merge top level modules. |
| 118 for (let i = 0; i < f.statements.length; ++i) { | 208 for (let i = 0; i < f.statements.length; ++i) { |
| 119 let statement = f.statements[i]; | 209 let statement = f.statements[i]; |
| 120 if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue; | 210 if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue; |
| 121 let moduleDecl = <ts.ModuleDeclaration>statement; | 211 let moduleDecl = <ts.ModuleDeclaration>statement; |
| 122 let name = moduleDecl.name.text; | 212 let name = moduleDecl.name.text; |
| 123 if (modules.hasOwnProperty(name)) { | 213 if (Object.hasOwnProperty.call(modules, name)) { |
| 124 let srcBody = modules[name].body; | 214 let srcBody = modules[name].body; |
| 125 let srcBodyBlock: ts.ModuleBlock; | 215 let srcBodyBlock: ts.ModuleBlock; |
| 126 | 216 |
| 127 if (srcBody.kind !== ts.SyntaxKind.ModuleBlock) { | 217 if (srcBody.kind !== ts.SyntaxKind.ModuleBlock) { |
| 128 throw 'Module body must be a module block.'; | 218 throw 'Module body must be a module block.'; |
| 129 } | 219 } |
| 130 srcBodyBlock = <ts.ModuleBlock>srcBody; | 220 srcBodyBlock = <ts.ModuleBlock>srcBody; |
| 131 | 221 |
| 132 let body = moduleDecl.body; | 222 let body = moduleDecl.body; |
| 133 if (body.kind === ts.SyntaxKind.ModuleBlock) { | 223 if (body.kind === ts.SyntaxKind.ModuleBlock) { |
| (...skipping 21 matching lines...) Expand all Loading... |
| 155 } | 245 } |
| 156 | 246 |
| 157 function mergeVariablesIntoClasses(n: ts.Node, classes: {[name: string]: base.
ClassLike}) { | 247 function mergeVariablesIntoClasses(n: ts.Node, classes: {[name: string]: base.
ClassLike}) { |
| 158 switch (n.kind) { | 248 switch (n.kind) { |
| 159 case ts.SyntaxKind.VariableStatement: | 249 case ts.SyntaxKind.VariableStatement: |
| 160 let statement = <ts.VariableStatement>n; | 250 let statement = <ts.VariableStatement>n; |
| 161 statement.declarationList.declarations.forEach(function( | 251 statement.declarationList.declarations.forEach(function( |
| 162 declaration: ts.VariableDeclaration) { | 252 declaration: ts.VariableDeclaration) { |
| 163 if (declaration.name.kind === ts.SyntaxKind.Identifier) { | 253 if (declaration.name.kind === ts.SyntaxKind.Identifier) { |
| 164 let name: string = (<ts.Identifier>(declaration.name)).text; | 254 let name: string = (<ts.Identifier>(declaration.name)).text; |
| 165 if (classes.hasOwnProperty(name)) { | 255 let existingClass = Object.hasOwnProperty.call(classes, name); |
| 256 let hasConstructor = false; |
| 257 if (declaration.type) { |
| 258 let type: ts.TypeNode = declaration.type; |
| 259 if (type.kind === ts.SyntaxKind.TypeLiteral) { |
| 260 let literal = <ts.TypeLiteralNode>type; |
| 261 hasConstructor = literal.members.some((member: ts.Node) => { |
| 262 return member.kind === ts.SyntaxKind.ConstructSignature; |
| 263 }); |
| 264 } else if (type.kind === ts.SyntaxKind.TypeReference) { |
| 265 // Handle interfaces with constructors. As Dart does not support
calling arbitrary |
| 266 // functions like constructors we need to upgrade the interface
to be a class |
| 267 // so we call invoke the constructor on the interface class. |
| 268 // Example typescript library definition matching this pattern: |
| 269 // |
| 270 // interface XStatic { |
| 271 // new (a: string, b): XStatic; |
| 272 // foo(); |
| 273 // } |
| 274 // |
| 275 // declare var X: XStatic; |
| 276 // |
| 277 // In JavaScript you could just write new X() and create an |
| 278 // instance of XStatic. We don't |
| 279 let typeRef = type as ts.TypeReferenceNode; |
| 280 let typeName = typeRef.typeName; |
| 281 let symbol = fc.tc.getSymbolAtLocation(typeName); |
| 282 if (symbol == null) return; |
| 283 let decl = fc.getSymbolDeclaration(symbol, typeName); |
| 284 if (decl == null) return; |
| 285 if (decl.kind !== ts.SyntaxKind.InterfaceDeclaration) return; |
| 286 let interfaceDecl = decl as base.ExtendedInterfaceDeclaration; |
| 287 if (!interfaceDecl.members.some( |
| 288 (member) => { return member.kind === ts.SyntaxKind.Const
ructSignature; })) |
| 289 return; |
| 290 |
| 291 if (interfaceDecl.classLikeVariableDeclaration == null) { |
| 292 // We could add extra logic to be safer such as only infering
that variable names |
| 293 // are class like for cases where variable names are UpperCame
lCase matching JS |
| 294 // conventions that a variable is a Class definition. |
| 295 interfaceDecl.classLikeVariableDeclaration = declaration; |
| 296 } |
| 297 } |
| 298 } |
| 299 |
| 300 if (existingClass || hasConstructor) { |
| 301 if (!existingClass) { |
| 302 // Create a stub existing class to upgrade the object literal to
if there is not an |
| 303 // existing class with the same name. |
| 304 let clazz = <ts.ClassDeclaration>ts.createNode(ts.SyntaxKind.Cla
ssDeclaration); |
| 305 base.copyLocation(declaration, clazz); |
| 306 clazz.name = declaration.name as ts.Identifier; |
| 307 clazz.members = <ts.NodeArray<ts.ClassElement>>[]; |
| 308 base.copyLocation(declaration, clazz.members); |
| 309 replaceNode(n, clazz); |
| 310 classes[name] = clazz; |
| 311 } |
| 312 |
| 166 let existing = classes[name]; | 313 let existing = classes[name]; |
| 314 if (existing.kind === ts.SyntaxKind.InterfaceDeclaration) { |
| 315 let interfaceDecl = existing as base.ExtendedInterfaceDeclaratio
n; |
| 316 // It is completely safe to assume that we know the precise clas
s like variable |
| 317 // declaration for the interface in this case as they have the s
ame exact name. |
| 318 interfaceDecl.classLikeVariableDeclaration = declaration; |
| 319 } |
| 167 let members = existing.members as Array<ts.ClassElement>; | 320 let members = existing.members as Array<ts.ClassElement>; |
| 168 if (declaration.type) { | 321 if (declaration.type) { |
| 169 let type: ts.TypeNode = declaration.type; | 322 let type: ts.TypeNode = declaration.type; |
| 170 if (type.kind === ts.SyntaxKind.TypeLiteral) { | 323 if (type.kind === ts.SyntaxKind.TypeLiteral) { |
| 171 removeNode(n); | 324 if (existingClass) { |
| 325 removeNode(n); |
| 326 } |
| 172 let literal = <ts.TypeLiteralNode>type; | 327 let literal = <ts.TypeLiteralNode>type; |
| 173 literal.members.forEach((member: ts.Node) => { | 328 literal.members.forEach((member: ts.Node) => { |
| 174 switch (member.kind) { | 329 switch (member.kind) { |
| 175 case ts.SyntaxKind.ConstructSignature: | 330 case ts.SyntaxKind.ConstructSignature: |
| 176 let signature: any = member; | 331 let signature: any = member; |
| 177 let constructor = | 332 let constructor = |
| 178 <ts.ConstructorDeclaration>ts.createNode(ts.SyntaxKi
nd.Constructor); | 333 <ts.ConstructorDeclaration>ts.createNode(ts.SyntaxKi
nd.Constructor); |
| 179 constructor.parameters = signature.parameters; | 334 constructor.parameters = signature.parameters; |
| 180 constructor.type = signature.type; | 335 constructor.type = signature.type; |
| 181 base.copyLocation(signature, constructor); | 336 base.copyLocation(signature, constructor); |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 242 break; | 397 break; |
| 243 case ts.SyntaxKind.SourceFile: | 398 case ts.SyntaxKind.SourceFile: |
| 244 let sourceFile = <ts.SourceFile>parent; | 399 let sourceFile = <ts.SourceFile>parent; |
| 245 removeFromArray(sourceFile.statements, n); | 400 removeFromArray(sourceFile.statements, n); |
| 246 break; | 401 break; |
| 247 default: | 402 default: |
| 248 throw 'removeNode not implemented for kind:' + parent.kind; | 403 throw 'removeNode not implemented for kind:' + parent.kind; |
| 249 } | 404 } |
| 250 } | 405 } |
| 251 | 406 |
| 252 function makeCallableClassesImplementFunction(decl: base.ClassLike) { | 407 function replaceInArray(nodes: ts.NodeArray<ts.Node>, v: ts.Node, replacement:
ts.Node) { |
| 253 if (base.isCallable(decl)) { | 408 for (let i = 0, len = nodes.length; i < len; ++i) { |
| 254 // Modify the AST to explicitly state that the class implements Function | 409 if (nodes[i] === v) { |
| 255 if (!decl.heritageClauses) { | 410 nodes[i] = replacement; |
| 256 decl.heritageClauses = <ts.NodeArray<ts.HeritageClause>>[]; | 411 break; |
| 257 base.copyLocation(decl, decl.heritageClauses); | |
| 258 } | 412 } |
| 259 let clauses = decl.heritageClauses; | |
| 260 let clause = base.arrayFindPolyfill( | |
| 261 clauses, (c) => c.token !== ts.SyntaxKind.ExtendsKeyword || | |
| 262 decl.kind === ts.SyntaxKind.InterfaceDeclaration); | |
| 263 if (clause == null) { | |
| 264 clause = <ts.HeritageClause>ts.createNode(ts.SyntaxKind.HeritageClause); | |
| 265 clause.token = decl.kind === ts.SyntaxKind.InterfaceDeclaration ? | |
| 266 ts.SyntaxKind.ExtendsKeyword : | |
| 267 ts.SyntaxKind.ImplementsKeyword; | |
| 268 clause.types = <ts.NodeArray<ts.ExpressionWithTypeArguments>>[]; | |
| 269 clause.parent = decl; | |
| 270 base.copyLocation(decl, clause); | |
| 271 clauses.push(clause); | |
| 272 } | |
| 273 let functionType = | |
| 274 <ts.ExpressionWithTypeArguments>ts.createNode(ts.SyntaxKind.Expression
WithTypeArguments); | |
| 275 functionType.parent = clause; | |
| 276 base.copyLocation(clause, functionType); | |
| 277 let fn = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); | |
| 278 fn.text = 'Function'; | |
| 279 fn.parent = functionType; | |
| 280 base.copyLocation(functionType, fn); | |
| 281 functionType.expression = fn; | |
| 282 clause.types.push(functionType); | |
| 283 } | 413 } |
| 284 } | 414 } |
| 285 | 415 |
| 416 function replaceNode(n: ts.Node, replacement: ts.Node) { |
| 417 let parent = n.parent; |
| 418 replacement.parent = parent; |
| 419 switch (parent.kind) { |
| 420 case ts.SyntaxKind.ModuleBlock: |
| 421 let block = <ts.ModuleBlock>parent; |
| 422 replaceInArray(block.statements, n, replacement); |
| 423 break; |
| 424 case ts.SyntaxKind.SourceFile: |
| 425 let sourceFile = <ts.SourceFile>parent; |
| 426 replaceInArray(sourceFile.statements, n, replacement); |
| 427 break; |
| 428 default: |
| 429 throw 'replaceNode not implemented for kind:' + parent.kind; |
| 430 } |
| 431 } |
| 432 |
| 286 function gatherClasses(n: ts.Node, classes: {[name: string]: base.ClassLike})
{ | 433 function gatherClasses(n: ts.Node, classes: {[name: string]: base.ClassLike})
{ |
| 287 switch (n.kind) { | 434 switch (n.kind) { |
| 288 case ts.SyntaxKind.ClassDeclaration: | 435 case ts.SyntaxKind.ClassDeclaration: |
| 289 case ts.SyntaxKind.InterfaceDeclaration: | 436 case ts.SyntaxKind.InterfaceDeclaration: |
| 290 let classDecl = <base.ClassLike>n; | 437 let classDecl = <base.ClassLike>n; |
| 291 let name = classDecl.name.text; | 438 let name = classDecl.name.text; |
| 292 // TODO(jacobr): validate that the classes have consistent | 439 // TODO(jacobr): validate that the classes have consistent |
| 293 // modifiers, etc. | 440 // modifiers, etc. |
| 294 if (classes.hasOwnProperty(name)) { | 441 if (Object.hasOwnProperty.call(classes, name)) { |
| 295 let existing = classes[name]; | 442 let existing = classes[name]; |
| 296 (classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElem
ent) => { | 443 (classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElem
ent) => { |
| 297 (existing.members as Array<ts.ClassElement>).push(e); | 444 (existing.members as Array<ts.ClassElement>).push(e); |
| 298 e.parent = existing; | 445 e.parent = existing; |
| 299 }); | 446 }); |
| 300 removeNode(classDecl); | 447 removeNode(classDecl); |
| 301 } else { | 448 } else { |
| 302 classes[name] = classDecl; | 449 classes[name] = classDecl; |
| 303 // Perform other class level post processing here. | 450 // Perform other class level post processing here. |
| 304 makeCallableClassesImplementFunction(classDecl); | |
| 305 } | 451 } |
| 306 break; | 452 break; |
| 307 case ts.SyntaxKind.ModuleDeclaration: | 453 case ts.SyntaxKind.ModuleDeclaration: |
| 308 case ts.SyntaxKind.SourceFile: | 454 case ts.SyntaxKind.SourceFile: |
| 309 let moduleClasses: {[name: string]: base.ClassLike} = {}; | 455 let moduleClasses: {[name: string]: base.ClassLike} = {}; |
| 310 ts.forEachChild(n, (child) => gatherClasses(child, moduleClasses)); | 456 ts.forEachChild(n, (child) => gatherClasses(child, moduleClasses)); |
| 311 ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, moduleCla
sses)); | 457 ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, moduleCla
sses)); |
| 312 | 458 |
| 313 break; | 459 break; |
| 314 case ts.SyntaxKind.ModuleBlock: | 460 case ts.SyntaxKind.ModuleBlock: |
| 315 ts.forEachChild(n, (child) => gatherClasses(child, classes)); | 461 ts.forEachChild(n, (child) => gatherClasses(child, classes)); |
| 316 break; | 462 break; |
| 317 default: | 463 default: |
| 318 break; | 464 break; |
| 319 } | 465 } |
| 320 } | 466 } |
| 321 gatherClasses(f, {}); | 467 gatherClasses(f, {}); |
| 322 } | 468 } |
| OLD | NEW |