| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 class LibraryImport { | |
| 6 final String prefix; | |
| 7 final Library library; | |
| 8 final SourceSpan span; | |
| 9 LibraryImport(this.library, [this.prefix, this.span]); | |
| 10 } | |
| 11 | |
| 12 // TODO(jimhug): Make this more useful for good error messages. | |
| 13 class AmbiguousMember extends Member { | |
| 14 List<Member> members; | |
| 15 AmbiguousMember(String name, this.members): super(name, null); | |
| 16 } | |
| 17 | |
| 18 | |
| 19 /** Represents a Dart library. */ | |
| 20 class Library extends Element { | |
| 21 final SourceFile baseSource; | |
| 22 Map<String, DefinedType> types; | |
| 23 List<LibraryImport> imports; | |
| 24 String sourceDir; | |
| 25 List<SourceFile> natives; | |
| 26 List<SourceFile> sources; | |
| 27 | |
| 28 Map<String, Member> _topNames; | |
| 29 Map<String, MemberSet> _privateMembers; | |
| 30 | |
| 31 /** The type that holds top level types in the library. */ | |
| 32 DefinedType topType; | |
| 33 | |
| 34 /** Set to true by [WorldGenerator] once this type has been written. */ | |
| 35 bool isWritten = false; | |
| 36 | |
| 37 Library(this.baseSource) : super(null, null) { | |
| 38 sourceDir = dirname(baseSource.filename); | |
| 39 topType = new DefinedType(null, this, null, true); | |
| 40 types = { '': topType }; | |
| 41 imports = []; | |
| 42 natives = []; | |
| 43 sources = []; | |
| 44 _privateMembers = {}; | |
| 45 } | |
| 46 | |
| 47 Element get enclosingElement() => null; | |
| 48 Library get library() => this; | |
| 49 | |
| 50 bool get isNative() => topType.isNative; | |
| 51 | |
| 52 bool get isCore() => this == world.corelib; | |
| 53 bool get isCoreImpl() => this == world.coreimpl; | |
| 54 | |
| 55 // TODO(jmesserly): we shouldn't be special casing DOM anywhere. | |
| 56 bool get isDomOrHtml() => this == world.dom || this == world.html; | |
| 57 | |
| 58 SourceSpan get span() => new SourceSpan(baseSource, 0, 0); | |
| 59 | |
| 60 String makeFullPath(String filename) { | |
| 61 if (filename.startsWith('dart:')) return filename; | |
| 62 if (filename.startsWith('package:')) return filename; | |
| 63 // TODO(jmesserly): replace with node.js path.resolve | |
| 64 if (filename.startsWith('/')) return filename; | |
| 65 if (filename.startsWith('file:///')) return filename; | |
| 66 if (filename.startsWith('http://')) return filename; | |
| 67 if (const RegExp('^[a-zA-Z]:/').hasMatch(filename)) return filename; | |
| 68 return joinPaths(sourceDir, filename); | |
| 69 } | |
| 70 | |
| 71 /** Adds an import to the library. */ | |
| 72 addImport(String fullname, String prefix, SourceSpan span) { | |
| 73 var newLib = world.getOrAddLibrary(fullname); | |
| 74 // Special exemption in spec to ensure core is only imported once | |
| 75 if (newLib.isCore) return; | |
| 76 imports.add(new LibraryImport(newLib, prefix, span)); | |
| 77 return newLib; | |
| 78 } | |
| 79 | |
| 80 addNative(String fullname) { | |
| 81 natives.add(world.reader.readFile(fullname)); | |
| 82 } | |
| 83 | |
| 84 MemberSet _findMembers(String name) { | |
| 85 if (name.startsWith('_')) { | |
| 86 return _privateMembers[name]; | |
| 87 } else { | |
| 88 return world._members[name]; | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 _addMember(Member member) { | |
| 93 if (member.isPrivate) { | |
| 94 if (member.isStatic) { | |
| 95 if (member.declaringType.isTop) { | |
| 96 world._addTopName(member); | |
| 97 } | |
| 98 } else { | |
| 99 var members = _privateMembers[member.name]; | |
| 100 if (members == null) { | |
| 101 members = new MemberSet(member, isVar: true); | |
| 102 _privateMembers[member.name] = members; | |
| 103 } else { | |
| 104 members.add(member); | |
| 105 } | |
| 106 } | |
| 107 } else { | |
| 108 world._addMember(member); | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 // TODO(jimhug): Cache and share the types as interfaces! | |
| 113 Type getOrAddFunctionType(Element enclosingElement, String name, | |
| 114 FunctionDefinition func, MethodData data) { | |
| 115 // TODO(jimhug): This is redundant now that FunctionDef has type params. | |
| 116 final def = new FunctionTypeDefinition(func, null, func.span); | |
| 117 final type = new DefinedType(name, this, def, false); | |
| 118 type.addMethod(':call', func); | |
| 119 var m = type.members[':call']; | |
| 120 m.enclosingElement = enclosingElement; | |
| 121 m.resolve(); | |
| 122 m._methodData = data; | |
| 123 // Function types implement the Function interface. | |
| 124 type.interfaces = [world.functionType]; | |
| 125 return type; | |
| 126 } | |
| 127 | |
| 128 /** Adds a type to the library. */ | |
| 129 DefinedType addType(String name, Node definition, bool isClass) { | |
| 130 if (types.containsKey(name)) { | |
| 131 var existingType = types[name]; | |
| 132 if ((isCore || isCoreImpl) && existingType.definition == null) { | |
| 133 // TODO(jimhug): Validate compatibility with natives. | |
| 134 existingType.setDefinition(definition); | |
| 135 } else { | |
| 136 world.warning('duplicate definition of $name', definition.span, | |
| 137 existingType.span); | |
| 138 } | |
| 139 } else { | |
| 140 types[name] = new DefinedType(name, this, definition, isClass); | |
| 141 } | |
| 142 | |
| 143 return types[name]; | |
| 144 } | |
| 145 | |
| 146 Type findType(NameTypeReference type) { | |
| 147 Type result = findTypeByName(type.name.name); | |
| 148 if (result == null) return null; | |
| 149 | |
| 150 if (type.names != null) { | |
| 151 if (type.names.length > 1) { | |
| 152 // TODO(jmesserly): can we ever get legitimate types with more than one | |
| 153 // name after the library prefix? | |
| 154 return null; | |
| 155 } | |
| 156 | |
| 157 if (!result.isTop) { | |
| 158 // No inner type support. If we get first-class types, this should | |
| 159 // perform a lookup on the type. | |
| 160 return null; | |
| 161 } | |
| 162 | |
| 163 // The type we got back was the "top level" library type. | |
| 164 // Now perform a lookup in that library for the next name. | |
| 165 return result.library.findTypeByName(type.names[0].name); | |
| 166 } | |
| 167 return result; | |
| 168 } | |
| 169 | |
| 170 // TODO(jimhug): Should be merged with new lookup method's logic. | |
| 171 Type findTypeByName(String name) { | |
| 172 var ret = types[name]; | |
| 173 | |
| 174 // Check all imports even if ret != null to detect conflicting names. | |
| 175 // TODO(jimhug): Only do this on first lookup. | |
| 176 for (var imported in imports) { | |
| 177 var newRet = null; | |
| 178 if (imported.prefix == null) { | |
| 179 newRet = imported.library.types[name]; | |
| 180 } else if (imported.prefix == name) { | |
| 181 newRet = imported.library.topType; | |
| 182 } | |
| 183 if (newRet != null) { | |
| 184 // TODO(jimhug): Should not need ret != newRet here or below. | |
| 185 if (ret != null && ret != newRet) { | |
| 186 world.error('conflicting types for "$name"', ret.span, newRet.span); | |
| 187 } else { | |
| 188 ret = newRet; | |
| 189 } | |
| 190 } | |
| 191 } | |
| 192 return ret; | |
| 193 } | |
| 194 | |
| 195 | |
| 196 // TODO(jimhug): Why is it okay to assume node is NameTypeReference in here? | |
| 197 Type resolveType(TypeReference node, bool typeErrors, bool allowTypeParams) { | |
| 198 if (node == null) return world.varType; | |
| 199 | |
| 200 var ret = findType(node); | |
| 201 | |
| 202 if (ret == null) { | |
| 203 var message = 'cannot find type ${_getDottedName(node)}'; | |
| 204 if (typeErrors) { | |
| 205 world.error(message, node.span); | |
| 206 return world.objectType; | |
| 207 } else { | |
| 208 world.warning(message, node.span); | |
| 209 return world.varType; | |
| 210 } | |
| 211 } | |
| 212 return ret; | |
| 213 } | |
| 214 | |
| 215 static String _getDottedName(NameTypeReference type) { | |
| 216 if (type.names != null) { | |
| 217 var names = map(type.names, (n) => n.name); | |
| 218 return type.name.name + '.' + Strings.join(names, '.'); | |
| 219 } else { | |
| 220 return type.name.name; | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 Member lookup(String name, SourceSpan span) { | |
| 225 return _topNames[name]; | |
| 226 } | |
| 227 | |
| 228 resolve() { | |
| 229 if (name == null) { | |
| 230 // TODO(jimhug): More fodder for io library. | |
| 231 name = baseSource.filename; | |
| 232 var index = name.lastIndexOf('/', name.length); | |
| 233 if (index >= 0) { | |
| 234 name = name.substring(index+1); | |
| 235 } | |
| 236 index = name.indexOf('.'); | |
| 237 if (index > 0) { | |
| 238 name = name.substring(0, index); | |
| 239 } | |
| 240 } | |
| 241 // TODO(jimhug): Expand to handle all illegal id characters | |
| 242 _jsname = | |
| 243 name.replaceAll('.', '_').replaceAll(':', '_').replaceAll(' ', '_'); | |
| 244 | |
| 245 for (var type in types.getValues()) { | |
| 246 type.resolve(); | |
| 247 } | |
| 248 } | |
| 249 | |
| 250 _addTopName(String name, Member member, [SourceSpan localSpan]) { | |
| 251 var existing = _topNames[name]; | |
| 252 if (existing === null) { | |
| 253 _topNames[name] = member; | |
| 254 } else { | |
| 255 if (existing is AmbiguousMember) { | |
| 256 existing.members.add(member); | |
| 257 } else { | |
| 258 var newMember = new AmbiguousMember(name, [existing, member]); | |
| 259 world.error('conflicting members for "$name"', | |
| 260 existing.span, member.span, localSpan); | |
| 261 _topNames[name] = newMember; | |
| 262 } | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 _addTopNames(Library lib) { | |
| 267 for (var member in lib.topType.members.getValues()) { | |
| 268 if (member.isPrivate && lib != this) continue; | |
| 269 _addTopName(member.name, member); | |
| 270 } | |
| 271 for (var type in lib.types.getValues()) { | |
| 272 if (!type.isTop) { | |
| 273 if (lib != this && type.typeMember.isPrivate) continue; | |
| 274 _addTopName(type.name, type.typeMember); | |
| 275 } | |
| 276 } | |
| 277 } | |
| 278 | |
| 279 /** | |
| 280 * This method will check for any conflicts in top-level names in this | |
| 281 * library. It will also build up a map from top-level names to a single | |
| 282 * member to be used for future lookups both to keep error messages clean | |
| 283 * and as a minor perf optimization. | |
| 284 */ | |
| 285 postResolveChecks() { | |
| 286 _topNames = {}; | |
| 287 // check for conflicts between top-level names | |
| 288 _addTopNames(this); | |
| 289 for (var imported in imports) { | |
| 290 if (imported.prefix == null) { | |
| 291 _addTopNames(imported.library); | |
| 292 } else { | |
| 293 _addTopName(imported.prefix, imported.library.topType.typeMember, | |
| 294 imported.span); | |
| 295 } | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 visitSources() { | |
| 300 var visitor = new _LibraryVisitor(this); | |
| 301 visitor.addSource(baseSource); | |
| 302 } | |
| 303 | |
| 304 toString() => baseSource.filename; | |
| 305 | |
| 306 int hashCode() => baseSource.filename.hashCode(); | |
| 307 | |
| 308 bool operator ==(other) => other is Library && | |
| 309 other.baseSource.filename == baseSource.filename; | |
| 310 } | |
| 311 | |
| 312 | |
| 313 class _LibraryVisitor implements TreeVisitor { | |
| 314 final Library library; | |
| 315 DefinedType currentType; | |
| 316 List<SourceFile> sources; | |
| 317 | |
| 318 bool seenImport = false; | |
| 319 bool seenSource = false; | |
| 320 bool seenResource = false; | |
| 321 bool isTop = true; | |
| 322 | |
| 323 _LibraryVisitor(this.library) { | |
| 324 currentType = library.topType; | |
| 325 sources = []; | |
| 326 } | |
| 327 | |
| 328 addSourceFromName(String name, SourceSpan span) { | |
| 329 var filename = library.makeFullPath(name); | |
| 330 if (filename == library.baseSource.filename) { | |
| 331 world.error('library cannot source itself', span); | |
| 332 return; | |
| 333 } else if (sources.some((s) => s.filename == filename)) { | |
| 334 world.error('file "$filename" has already been sourced', span); | |
| 335 return; | |
| 336 } | |
| 337 | |
| 338 var source = world.readFile(library.makeFullPath(name)); | |
| 339 sources.add(source); | |
| 340 } | |
| 341 | |
| 342 addSource(SourceFile source) { | |
| 343 if (library.sources.some((s) => s.filename == source.filename)) { | |
| 344 // TODO(jimhug): good error location. | |
| 345 world.error('duplicate source file "${source.filename}"', null); | |
| 346 return; | |
| 347 } | |
| 348 library.sources.add(source); | |
| 349 final parser = new Parser(source, diet: options.dietParse); | |
| 350 final unit = parser.compilationUnit(); | |
| 351 | |
| 352 unit.forEach((def) => def.visit(this)); | |
| 353 | |
| 354 assert(sources.length == 0 || isTop); | |
| 355 isTop = false; | |
| 356 var newSources = sources; | |
| 357 sources = []; | |
| 358 for (var newSource in newSources) { | |
| 359 addSource(newSource); | |
| 360 } | |
| 361 } | |
| 362 | |
| 363 void visitDirectiveDefinition(DirectiveDefinition node) { | |
| 364 if (!isTop) { | |
| 365 world.error('directives not allowed in sourced file', node.span); | |
| 366 return; | |
| 367 } | |
| 368 | |
| 369 var name; | |
| 370 switch (node.name.name) { | |
| 371 case "library": | |
| 372 name = getSingleStringArg(node); | |
| 373 if (library.name == null) { | |
| 374 library.name = name; | |
| 375 if (seenImport || seenSource || seenResource) { | |
| 376 world.error('#library must be first directive in file', node.span); | |
| 377 } | |
| 378 } else { | |
| 379 world.error('already specified library name', node.span); | |
| 380 } | |
| 381 break; | |
| 382 | |
| 383 case "import": | |
| 384 seenImport = true; | |
| 385 name = getFirstStringArg(node); | |
| 386 var prefix = tryGetNamedStringArg(node, 'prefix'); | |
| 387 if (node.arguments.length > 2 || | |
| 388 node.arguments.length == 2 && prefix == null) { | |
| 389 world.error( | |
| 390 'expected at most one "name" argument and one optional "prefix"' | |
| 391 + ' but found ${node.arguments.length}', node.span); | |
| 392 } else if (prefix != null && prefix.indexOf('.') >= 0) { | |
| 393 world.error('library prefix canot contain "."', node.span); | |
| 394 } else if (seenSource || seenResource) { | |
| 395 world.error('#imports must come before any #source or #resource', | |
| 396 node.span); | |
| 397 } | |
| 398 | |
| 399 // Empty prefix and no prefix are equivalent | |
| 400 if (prefix == '') prefix = null; | |
| 401 | |
| 402 var filename = library.makeFullPath(name); | |
| 403 | |
| 404 if (library.imports.some((li) => li.library.baseSource == filename)) { | |
| 405 // TODO(jimhug): Can you import a lib twice with different prefixes? | |
| 406 world.error('duplicate import of "$name"', node.span); | |
| 407 return; | |
| 408 } | |
| 409 | |
| 410 var newLib = library.addImport(filename, prefix, node.span); | |
| 411 // TODO(jimhug): Add check that imported library has a #library | |
| 412 break; | |
| 413 | |
| 414 case "source": | |
| 415 seenSource = true; | |
| 416 name = getSingleStringArg(node); | |
| 417 addSourceFromName(name, node.span); | |
| 418 if (seenResource) { | |
| 419 world.error('#sources must come before any #resource', node.span); | |
| 420 } | |
| 421 break; | |
| 422 | |
| 423 case "native": | |
| 424 // TODO(jimhug): Fit this into spec? | |
| 425 name = getSingleStringArg(node); | |
| 426 library.addNative(library.makeFullPath(name)); | |
| 427 break; | |
| 428 | |
| 429 case "resource": | |
| 430 // TODO(jmesserly): should we do anything else here? | |
| 431 seenResource = true; | |
| 432 getFirstStringArg(node); | |
| 433 break; | |
| 434 | |
| 435 default: | |
| 436 world.error('unknown directive: ${node.name.name}', node.span); | |
| 437 } | |
| 438 } | |
| 439 | |
| 440 String getSingleStringArg(DirectiveDefinition node) { | |
| 441 if (node.arguments.length != 1) { | |
| 442 world.error( | |
| 443 'expected exactly one argument but found ${node.arguments.length}', | |
| 444 node.span); | |
| 445 } | |
| 446 return getFirstStringArg(node); | |
| 447 } | |
| 448 | |
| 449 String getFirstStringArg(DirectiveDefinition node) { | |
| 450 if (node.arguments.length < 1) { | |
| 451 world.error( | |
| 452 'expected at least one argument but found ${node.arguments.length}', | |
| 453 node.span); | |
| 454 } | |
| 455 var arg = node.arguments[0]; | |
| 456 if (arg.label != null) { | |
| 457 world.error('label not allowed for directive', node.span); | |
| 458 } | |
| 459 return _parseStringArgument(arg); | |
| 460 } | |
| 461 | |
| 462 String tryGetNamedStringArg(DirectiveDefinition node, String argName) { | |
| 463 var args = node.arguments.filter( | |
| 464 (a) => a.label != null && a.label.name == argName); | |
| 465 | |
| 466 if (args.length == 0) { | |
| 467 return null; | |
| 468 } | |
| 469 if (args.length > 1) { | |
| 470 world.error('expected at most one "${argName}" argument but found ' | |
| 471 + node.arguments.length, node.span); | |
| 472 } | |
| 473 // Even though the collection has one arg, this is the easiest way to get | |
| 474 // the first item. | |
| 475 for (var arg in args) { | |
| 476 return _parseStringArgument(arg); | |
| 477 } | |
| 478 } | |
| 479 | |
| 480 String _parseStringArgument(ArgumentNode arg) { | |
| 481 var expr = arg.value; | |
| 482 if (expr is! LiteralExpression || !expr.value.type.isString) { | |
| 483 world.error('expected string literal', expr.span); | |
| 484 } | |
| 485 return expr.value.actualValue; | |
| 486 } | |
| 487 | |
| 488 void visitTypeDefinition(TypeDefinition node) { | |
| 489 var oldType = currentType; | |
| 490 currentType = library.addType(node.name.name, node, node.isClass); | |
| 491 for (var member in node.body) { | |
| 492 member.visit(this); | |
| 493 } | |
| 494 currentType = oldType; | |
| 495 } | |
| 496 | |
| 497 void visitVariableDefinition(VariableDefinition node) { | |
| 498 currentType.addField(node); | |
| 499 } | |
| 500 | |
| 501 void visitFunctionDefinition(FunctionDefinition node) { | |
| 502 currentType.addMethod(node.name.name, node); | |
| 503 } | |
| 504 | |
| 505 void visitFunctionTypeDefinition(FunctionTypeDefinition node) { | |
| 506 var type = library.addType(node.func.name.name, node, false); | |
| 507 type.addMethod(':call', node.func); | |
| 508 } | |
| 509 } | |
| OLD | NEW |