001/* 002 * Copyright 2010-2013 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.jetbrains.k2js.translate.context; 018 019import com.google.common.collect.Maps; 020import com.google.dart.compiler.backend.js.ast.*; 021import com.intellij.psi.PsiElement; 022import com.intellij.psi.PsiFile; 023import org.jetbrains.annotations.NotNull; 024import org.jetbrains.annotations.Nullable; 025import org.jetbrains.jet.lang.descriptors.*; 026import org.jetbrains.jet.lang.resolve.BindingContext; 027import org.jetbrains.jet.lang.resolve.BindingContextUtils; 028import org.jetbrains.jet.lang.resolve.DescriptorUtils; 029import org.jetbrains.k2js.config.EcmaVersion; 030import org.jetbrains.k2js.config.LibrarySourcesConfig; 031import org.jetbrains.k2js.translate.context.generator.Generator; 032import org.jetbrains.k2js.translate.context.generator.Rule; 033import org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator; 034import org.jetbrains.k2js.translate.intrinsic.Intrinsics; 035import org.jetbrains.k2js.translate.utils.AnnotationsUtils; 036import org.jetbrains.k2js.translate.utils.JsAstUtils; 037import org.jetbrains.k2js.translate.utils.JsDescriptorUtils; 038import org.jetbrains.k2js.translate.utils.PredefinedAnnotation; 039 040import java.util.Map; 041 042import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.*; 043import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration; 044import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*; 045 046/** 047 * Aggregates all the static parts of the context. 048 */ 049public final class StaticContext { 050 051 public static StaticContext generateStaticContext(@NotNull BindingContext bindingContext, @NotNull EcmaVersion ecmaVersion) { 052 JsProgram program = new JsProgram("main"); 053 Namer namer = Namer.newInstance(program.getRootScope()); 054 Intrinsics intrinsics = new Intrinsics(); 055 StandardClasses standardClasses = StandardClasses.bindImplementations(namer.getKotlinScope()); 056 return new StaticContext(program, bindingContext, namer, intrinsics, standardClasses, program.getRootScope(), ecmaVersion); 057 } 058 059 @NotNull 060 private final JsProgram program; 061 062 @NotNull 063 private final BindingContext bindingContext; 064 @NotNull 065 private final Namer namer; 066 067 @NotNull 068 private final Intrinsics intrinsics; 069 070 @NotNull 071 private final StandardClasses standardClasses; 072 073 @NotNull 074 private final JsScope rootScope; 075 076 @NotNull 077 private final Generator<JsName> names = new NameGenerator(); 078 @NotNull 079 private final Generator<JsScope> scopes = new ScopeGenerator(); 080 @NotNull 081 private final Generator<JsNameRef> qualifiers = new QualifierGenerator(); 082 @NotNull 083 private final Generator<Boolean> qualifierIsNull = new QualifierIsNullGenerator(); 084 085 @NotNull 086 private final Map<JsScope, JsFunction> scopeToFunction = Maps.newHashMap(); 087 088 @NotNull 089 private final EcmaVersion ecmaVersion; 090 091 @NotNull 092 private final LiteralFunctionTranslator literalFunctionTranslator = new LiteralFunctionTranslator(); 093 094 //TODO: too many parameters in constructor 095 private StaticContext(@NotNull JsProgram program, @NotNull BindingContext bindingContext, 096 @NotNull Namer namer, @NotNull Intrinsics intrinsics, 097 @NotNull StandardClasses standardClasses, @NotNull JsScope rootScope, @NotNull EcmaVersion ecmaVersion) { 098 this.program = program; 099 this.bindingContext = bindingContext; 100 this.namer = namer; 101 this.intrinsics = intrinsics; 102 this.rootScope = rootScope; 103 this.standardClasses = standardClasses; 104 this.ecmaVersion = ecmaVersion; 105 } 106 107 @NotNull 108 public LiteralFunctionTranslator getLiteralFunctionTranslator() { 109 return literalFunctionTranslator; 110 } 111 112 public boolean isEcma5() { 113 return ecmaVersion == EcmaVersion.v5; 114 } 115 116 @NotNull 117 public EcmaVersion getEcmaVersion() { 118 return ecmaVersion; 119 } 120 121 @NotNull 122 public JsProgram getProgram() { 123 return program; 124 } 125 126 @NotNull 127 public BindingContext getBindingContext() { 128 return bindingContext; 129 } 130 131 @NotNull 132 public Intrinsics getIntrinsics() { 133 return intrinsics; 134 } 135 136 @NotNull 137 public Namer getNamer() { 138 return namer; 139 } 140 141 @NotNull 142 public JsScope getRootScope() { 143 return rootScope; 144 } 145 146 @NotNull 147 public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) { 148 JsScope scope = scopes.get(descriptor.getOriginal()); 149 assert scope != null : "Must have a scope for descriptor"; 150 return scope; 151 } 152 153 @NotNull 154 public JsFunction getFunctionWithScope(@NotNull CallableDescriptor descriptor) { 155 JsScope scope = getScopeForDescriptor(descriptor); 156 JsFunction function = scopeToFunction.get(scope); 157 assert scope.equals(function.getScope()) : "Inconsistency."; 158 return function; 159 } 160 161 @NotNull 162 public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) { 163 JsName name = names.get(descriptor.getOriginal()); 164 assert name != null : "Must have name for descriptor"; 165 return name; 166 } 167 168 private final class NameGenerator extends Generator<JsName> { 169 private JsName declareName(DeclarationDescriptor descriptor, String name) { 170 JsScope scope = getEnclosingScope(descriptor); 171 // ecma 5 property name never declares as obfuscatable: 172 // 1) property cannot be overloaded, so, name collision is not possible 173 // 2) main reason: if property doesn't have any custom accessor, value holder will have the same name as accessor, so, the same name will be declared more than once 174 return isEcma5() ? scope.declareName(name) : scope.declareFreshName(name); 175 } 176 177 public NameGenerator() { 178 Rule<JsName> namesForStandardClasses = new Rule<JsName>() { 179 @Override 180 @Nullable 181 public JsName apply(@NotNull DeclarationDescriptor data) { 182 if (!standardClasses.isStandardObject(data)) { 183 return null; 184 } 185 return standardClasses.getStandardObjectName(data); 186 } 187 }; 188 Rule<JsName> namespacesShouldBeDefinedInRootScope = new Rule<JsName>() { 189 @Override 190 @Nullable 191 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 192 if (!(descriptor instanceof NamespaceDescriptor)) { 193 return null; 194 } 195 196 String name = Namer.generateNamespaceName(descriptor); 197 return getRootScope().declareName(name); 198 } 199 }; 200 Rule<JsName> memberDeclarationsInsideParentsScope = new Rule<JsName>() { 201 @Override 202 @Nullable 203 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 204 JsScope scope = getEnclosingScope(descriptor); 205 return scope.declareFreshName(descriptor.getName().asString()); 206 } 207 }; 208 Rule<JsName> constructorHasTheSameNameAsTheClass = new Rule<JsName>() { 209 @Override 210 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 211 if (!(descriptor instanceof ConstructorDescriptor)) { 212 return null; 213 } 214 ClassDescriptor containingClass = getContainingClass(descriptor); 215 assert containingClass != null : "Can't have constructor without a class"; 216 return getNameForDescriptor(containingClass); 217 } 218 }; 219 Rule<JsName> accessorsHasNamesWithSpecialPrefixes = new Rule<JsName>() { 220 @Override 221 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 222 if (!(descriptor instanceof PropertyAccessorDescriptor)) { 223 return null; 224 } 225 226 PropertyAccessorDescriptor accessorDescriptor = (PropertyAccessorDescriptor) descriptor; 227 String propertyName = accessorDescriptor.getCorrespondingProperty().getName().asString(); 228 if (isObjectDeclaration(bindingContext, accessorDescriptor.getCorrespondingProperty())) { 229 return declareName(descriptor, propertyName); 230 } 231 232 boolean isGetter = descriptor instanceof PropertyGetterDescriptor; 233 String accessorName = Namer.getNameForAccessor(propertyName, isGetter, 234 accessorDescriptor.getReceiverParameter() == null && isEcma5()); 235 return declareName(descriptor, accessorName); 236 } 237 }; 238 239 Rule<JsName> predefinedObjectsHasUnobfuscatableNames = new Rule<JsName>() { 240 @Override 241 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 242 for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) { 243 if (!hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) { 244 continue; 245 } 246 String name = getNameForAnnotatedObject(descriptor, annotation); 247 name = (name != null) ? name : descriptor.getName().asString(); 248 return getEnclosingScope(descriptor).declareName(name); 249 } 250 return null; 251 } 252 }; 253 Rule<JsName> propertiesCorrespondToSpeciallyTreatedBackingFieldNames = new Rule<JsName>() { 254 @Override 255 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 256 if (!(descriptor instanceof PropertyDescriptor)) { 257 return null; 258 } 259 260 String name = descriptor.getName().asString(); 261 if (!isEcma5() || JsDescriptorUtils.isAsPrivate((PropertyDescriptor) descriptor)) { 262 name = Namer.getKotlinBackingFieldName(name); 263 } 264 265 return declareName(descriptor, name); 266 } 267 }; 268 //TODO: hack! 269 Rule<JsName> toStringHack = new Rule<JsName>() { 270 @Override 271 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 272 if (!(descriptor instanceof FunctionDescriptor)) { 273 return null; 274 } 275 if (!descriptor.getName().asString().equals("toString")) { 276 return null; 277 } 278 if (((FunctionDescriptor) descriptor).getValueParameters().isEmpty()) { 279 return getEnclosingScope(descriptor).declareName("toString"); 280 } 281 return null; 282 } 283 }; 284 285 Rule<JsName> overridingDescriptorsReferToOriginalName = new Rule<JsName>() { 286 @Override 287 public JsName apply(@NotNull DeclarationDescriptor descriptor) { 288 //TODO: refactor 289 if (!(descriptor instanceof FunctionDescriptor)) { 290 return null; 291 } 292 FunctionDescriptor overriddenDescriptor = getOverriddenDescriptor((FunctionDescriptor) descriptor); 293 if (overriddenDescriptor == null) { 294 return null; 295 } 296 297 JsScope scope = getEnclosingScope(descriptor); 298 JsName result = getNameForDescriptor(overriddenDescriptor); 299 scope.declareName(result.getIdent()); 300 return result; 301 } 302 }; 303 addRule(namesForStandardClasses); 304 addRule(constructorHasTheSameNameAsTheClass); 305 addRule(predefinedObjectsHasUnobfuscatableNames); 306 addRule(toStringHack); 307 addRule(propertiesCorrespondToSpeciallyTreatedBackingFieldNames); 308 addRule(namespacesShouldBeDefinedInRootScope); 309 addRule(overridingDescriptorsReferToOriginalName); 310 addRule(accessorsHasNamesWithSpecialPrefixes); 311 addRule(memberDeclarationsInsideParentsScope); 312 } 313 } 314 315 @NotNull 316 private JsScope getEnclosingScope(@NotNull DeclarationDescriptor descriptor) { 317 DeclarationDescriptor containingDeclaration = getContainingDeclaration(descriptor); 318 return getScopeForDescriptor(containingDeclaration.getOriginal()); 319 } 320 321 private final class ScopeGenerator extends Generator<JsScope> { 322 323 public ScopeGenerator() { 324 Rule<JsScope> generateNewScopesForClassesWithNoAncestors = new Rule<JsScope>() { 325 @Override 326 public JsScope apply(@NotNull DeclarationDescriptor descriptor) { 327 if (!(descriptor instanceof ClassDescriptor)) { 328 return null; 329 } 330 if (getSuperclass((ClassDescriptor) descriptor) == null) { 331 return getRootScope().innerScope("Scope for class " + descriptor.getName()); 332 } 333 return null; 334 } 335 }; 336 Rule<JsScope> generateInnerScopesForDerivedClasses = new Rule<JsScope>() { 337 @Override 338 public JsScope apply(@NotNull DeclarationDescriptor descriptor) { 339 if (!(descriptor instanceof ClassDescriptor)) { 340 return null; 341 } 342 ClassDescriptor superclass = getSuperclass((ClassDescriptor) descriptor); 343 if (superclass == null) { 344 return null; 345 } 346 return getScopeForDescriptor(superclass).innerScope("Scope for class " + descriptor.getName()); 347 } 348 }; 349 Rule<JsScope> generateNewScopesForNamespaceDescriptors = new Rule<JsScope>() { 350 @Override 351 public JsScope apply(@NotNull DeclarationDescriptor descriptor) { 352 if (!(descriptor instanceof NamespaceDescriptor)) { 353 return null; 354 } 355 return getRootScope().innerScope("Namespace " + descriptor.getName()); 356 } 357 }; 358 //TODO: never get there 359 Rule<JsScope> generateInnerScopesForMembers = new Rule<JsScope>() { 360 @Override 361 public JsScope apply(@NotNull DeclarationDescriptor descriptor) { 362 JsScope enclosingScope = getEnclosingScope(descriptor); 363 return enclosingScope.innerScope("Scope for member " + descriptor.getName()); 364 } 365 }; 366 Rule<JsScope> createFunctionObjectsForCallableDescriptors = new Rule<JsScope>() { 367 @Override 368 public JsScope apply(@NotNull DeclarationDescriptor descriptor) { 369 if (!(descriptor instanceof CallableDescriptor)) { 370 return null; 371 } 372 JsScope enclosingScope = getEnclosingScope(descriptor); 373 374 JsFunction correspondingFunction = JsAstUtils.createFunctionWithEmptyBody(enclosingScope); 375 assert (!scopeToFunction.containsKey(correspondingFunction.getScope())) : "Scope to function value overridden for " + descriptor; 376 scopeToFunction.put(correspondingFunction.getScope(), correspondingFunction); 377 return correspondingFunction.getScope(); 378 } 379 }; 380 addRule(createFunctionObjectsForCallableDescriptors); 381 addRule(generateNewScopesForClassesWithNoAncestors); 382 addRule(generateInnerScopesForDerivedClasses); 383 addRule(generateNewScopesForNamespaceDescriptors); 384 addRule(generateInnerScopesForMembers); 385 } 386 } 387 388 @Nullable 389 public JsNameRef getQualifierForDescriptor(@NotNull DeclarationDescriptor descriptor) { 390 if (qualifierIsNull.get(descriptor.getOriginal()) != null) { 391 return null; 392 } 393 return qualifiers.get(descriptor.getOriginal()); 394 } 395 396 private final class QualifierGenerator extends Generator<JsNameRef> { 397 public QualifierGenerator() { 398 Rule<JsNameRef> standardObjectsHaveKotlinQualifier = new Rule<JsNameRef>() { 399 @Override 400 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) { 401 if (!standardClasses.isStandardObject(descriptor)) { 402 return null; 403 } 404 return namer.kotlinObject(); 405 } 406 }; 407 //TODO: review and refactor 408 Rule<JsNameRef> namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier = new Rule<JsNameRef>() { 409 @Override 410 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) { 411 DeclarationDescriptor containingDescriptor = getContainingDeclaration(descriptor); 412 if (!(containingDescriptor instanceof NamespaceDescriptor)) { 413 return null; 414 } 415 416 JsNameRef result = new JsNameRef(getNameForDescriptor(containingDescriptor)); 417 if (DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) { 418 return result; 419 } 420 421 JsNameRef qualifier = result; 422 while ((containingDescriptor = getContainingDeclaration(containingDescriptor)) instanceof NamespaceDescriptor && 423 !DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) { 424 JsNameRef ref = getNameForDescriptor(containingDescriptor).makeRef(); 425 qualifier.setQualifier(ref); 426 qualifier = ref; 427 } 428 429 PsiElement element = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor); 430 if (element == null && descriptor instanceof PropertyAccessorDescriptor) { 431 element = BindingContextUtils.descriptorToDeclaration(bindingContext, ((PropertyAccessorDescriptor) descriptor) 432 .getCorrespondingProperty()); 433 } 434 435 if (element != null) { 436 PsiFile file = element.getContainingFile(); 437 String moduleName = file.getUserData(LibrarySourcesConfig.EXTERNAL_MODULE_NAME); 438 if (LibrarySourcesConfig.UNKNOWN_EXTERNAL_MODULE_NAME.equals(moduleName)) { 439 return null; 440 } 441 else if (moduleName != null) { 442 qualifier.setQualifier(new JsArrayAccess(namer.kotlin("modules"), program.getStringLiteral(moduleName))); 443 } 444 else if (result == qualifier && result.getIdent().equals("kotlin")) { 445 // todo WebDemoExamples2Test#testBuilder, package "kotlin" from kotlin/js/js.libraries/src/stdlib/JUMaps.kt must be inlined 446 // return qualifier; 447 } 448 } 449 450 if (qualifier.getQualifier() == null) { 451 qualifier.setQualifier(new JsNameRef(Namer.getRootNamespaceName())); 452 } 453 454 return result; 455 } 456 }; 457 Rule<JsNameRef> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() { 458 @Override 459 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) { 460 if (!(descriptor instanceof ConstructorDescriptor)) { 461 return null; 462 } 463 ClassDescriptor containingClass = getContainingClass(descriptor); 464 assert containingClass != null : "Can't have constructor without a class"; 465 return getQualifierForDescriptor(containingClass); 466 } 467 }; 468 Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() { 469 @Override 470 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) { 471 if (isLibraryObject(descriptor)) { 472 return namer.kotlinObject(); 473 } 474 return null; 475 } 476 }; 477 addRule(libraryObjectsHaveKotlinQualifier); 478 addRule(constructorHaveTheSameQualifierAsTheClass); 479 addRule(standardObjectsHaveKotlinQualifier); 480 addRule(namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier); 481 } 482 } 483 484 private static class QualifierIsNullGenerator extends Generator<Boolean> { 485 486 private QualifierIsNullGenerator() { 487 Rule<Boolean> propertiesHaveNoQualifiers = new Rule<Boolean>() { 488 @Override 489 public Boolean apply(@NotNull DeclarationDescriptor descriptor) { 490 if (!(descriptor instanceof PropertyDescriptor)) { 491 return null; 492 } 493 return true; 494 } 495 }; 496 //TODO: hack! 497 Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() { 498 @Override 499 public Boolean apply(@NotNull DeclarationDescriptor descriptor) { 500 if (!AnnotationsUtils.isNativeObject(descriptor)) { 501 return null; 502 } 503 return true; 504 } 505 }; 506 Rule<Boolean> topLevelNamespaceHaveNoQualifier = new Rule<Boolean>() { 507 @Override 508 public Boolean apply(@NotNull DeclarationDescriptor descriptor) { 509 if (descriptor instanceof NamespaceDescriptor && DescriptorUtils.isRootNamespace((NamespaceDescriptor) descriptor)) { 510 return true; 511 } 512 return null; 513 } 514 }; 515 addRule(topLevelNamespaceHaveNoQualifier); 516 addRule(propertiesHaveNoQualifiers); 517 addRule(nativeObjectsHaveNoQualifiers); 518 } 519 } 520}