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
017 package org.jetbrains.k2js.translate.context;
018
019 import com.google.common.collect.Maps;
020 import com.google.dart.compiler.backend.js.ast.*;
021 import com.intellij.psi.PsiElement;
022 import com.intellij.psi.PsiFile;
023 import org.jetbrains.annotations.NotNull;
024 import org.jetbrains.annotations.Nullable;
025 import org.jetbrains.jet.lang.descriptors.*;
026 import org.jetbrains.jet.lang.resolve.BindingContext;
027 import org.jetbrains.jet.lang.resolve.BindingContextUtils;
028 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029 import org.jetbrains.k2js.config.EcmaVersion;
030 import org.jetbrains.k2js.config.LibrarySourcesConfig;
031 import org.jetbrains.k2js.translate.context.generator.Generator;
032 import org.jetbrains.k2js.translate.context.generator.Rule;
033 import org.jetbrains.k2js.translate.declaration.ClassDeclarationTranslator;
034 import org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator;
035 import org.jetbrains.k2js.translate.intrinsic.Intrinsics;
036 import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
037 import org.jetbrains.k2js.translate.utils.JsAstUtils;
038 import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
039 import org.jetbrains.k2js.translate.utils.PredefinedAnnotation;
040
041 import java.util.Arrays;
042 import java.util.Collection;
043 import java.util.Comparator;
044 import java.util.Map;
045
046 import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.*;
047 import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
048 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
049
050 /**
051 * Aggregates all the static parts of the context.
052 */
053 public final class StaticContext {
054
055 public static StaticContext generateStaticContext(@NotNull BindingContext bindingContext, @NotNull EcmaVersion ecmaVersion) {
056 JsProgram program = new JsProgram("main");
057 Namer namer = Namer.newInstance(program.getRootScope());
058 Intrinsics intrinsics = new Intrinsics();
059 StandardClasses standardClasses = StandardClasses.bindImplementations(namer.getKotlinScope());
060 return new StaticContext(program, bindingContext, namer, intrinsics, standardClasses, program.getRootScope(), ecmaVersion);
061 }
062
063 @NotNull
064 private final JsProgram program;
065
066 @NotNull
067 private final BindingContext bindingContext;
068 @NotNull
069 private final Namer namer;
070
071 @NotNull
072 private final Intrinsics intrinsics;
073
074 @NotNull
075 private final StandardClasses standardClasses;
076
077 @NotNull
078 private final JsScope rootScope;
079
080 @NotNull
081 private final Generator<JsName> names = new NameGenerator();
082 @NotNull
083 private final Generator<JsScope> scopes = new ScopeGenerator();
084 @NotNull
085 private final Generator<JsNameRef> qualifiers = new QualifierGenerator();
086 @NotNull
087 private final Generator<Boolean> qualifierIsNull = new QualifierIsNullGenerator();
088
089 @NotNull
090 private final Map<JsScope, JsFunction> scopeToFunction = Maps.newHashMap();
091
092 @NotNull
093 private final EcmaVersion ecmaVersion;
094
095 @NotNull
096 private LiteralFunctionTranslator literalFunctionTranslator;
097 @NotNull
098 private ClassDeclarationTranslator classDeclarationTranslator;
099
100 //TODO: too many parameters in constructor
101 private StaticContext(@NotNull JsProgram program, @NotNull BindingContext bindingContext,
102 @NotNull Namer namer, @NotNull Intrinsics intrinsics,
103 @NotNull StandardClasses standardClasses, @NotNull JsScope rootScope, @NotNull EcmaVersion ecmaVersion) {
104 this.program = program;
105 this.bindingContext = bindingContext;
106 this.namer = namer;
107 this.intrinsics = intrinsics;
108 this.rootScope = rootScope;
109 this.standardClasses = standardClasses;
110 this.ecmaVersion = ecmaVersion;
111 }
112
113 public void initTranslators(TranslationContext programContext) {
114 literalFunctionTranslator = new LiteralFunctionTranslator(programContext);
115 classDeclarationTranslator = new ClassDeclarationTranslator(programContext);
116 }
117
118 @NotNull
119 public LiteralFunctionTranslator getLiteralFunctionTranslator() {
120 return literalFunctionTranslator;
121 }
122
123 @NotNull
124 public ClassDeclarationTranslator getClassDeclarationTranslator() {
125 return classDeclarationTranslator;
126 }
127
128 public boolean isEcma5() {
129 return ecmaVersion == EcmaVersion.v5;
130 }
131
132 @NotNull
133 public JsProgram getProgram() {
134 return program;
135 }
136
137 @NotNull
138 public BindingContext getBindingContext() {
139 return bindingContext;
140 }
141
142 @NotNull
143 public Intrinsics getIntrinsics() {
144 return intrinsics;
145 }
146
147 @NotNull
148 public Namer getNamer() {
149 return namer;
150 }
151
152 @NotNull
153 public JsScope getRootScope() {
154 return rootScope;
155 }
156
157 @NotNull
158 public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
159 JsScope scope = scopes.get(descriptor.getOriginal());
160 assert scope != null : "Must have a scope for descriptor";
161 return scope;
162 }
163
164 @NotNull
165 public JsFunction getFunctionWithScope(@NotNull CallableDescriptor descriptor) {
166 JsScope scope = getScopeForDescriptor(descriptor);
167 JsFunction function = scopeToFunction.get(scope);
168 assert scope.equals(function.getScope()) : "Inconsistency.";
169 return function;
170 }
171
172 @NotNull
173 public JsNameRef getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
174 ClassDescriptor classDescriptor;
175 if (descriptor instanceof ConstructorDescriptor) {
176 classDescriptor = ((ConstructorDescriptor) descriptor).getContainingDeclaration();
177 }
178 else if (descriptor instanceof ClassDescriptor) {
179 classDescriptor = (ClassDescriptor) descriptor;
180 }
181 else {
182 classDescriptor = null;
183 }
184
185 if (classDescriptor != null) {
186 JsNameRef reference = classDeclarationTranslator.getQualifiedReference((classDescriptor));
187 if (reference != null) {
188 return reference;
189 }
190 }
191 return new JsNameRef(getNameForDescriptor(descriptor), getQualifierForDescriptor(descriptor));
192 }
193
194 @NotNull
195 public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
196 JsName name = names.get(descriptor.getOriginal());
197 assert name != null : "Must have name for descriptor";
198 return name;
199 }
200
201 private final class NameGenerator extends Generator<JsName> {
202 private JsName declareName(DeclarationDescriptor descriptor, String name) {
203 JsScope scope = getEnclosingScope(descriptor);
204 // ecma 5 property name never declares as obfuscatable:
205 // 1) property cannot be overloaded, so, name collision is not possible
206 // 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
207 return isEcma5() ? scope.declareName(name) : scope.declareFreshName(name);
208 }
209
210 public NameGenerator() {
211 Rule<JsName> namesForStandardClasses = new Rule<JsName>() {
212 @Override
213 @Nullable
214 public JsName apply(@NotNull DeclarationDescriptor data) {
215 if (!standardClasses.isStandardObject(data)) {
216 return null;
217 }
218 return standardClasses.getStandardObjectName(data);
219 }
220 };
221 Rule<JsName> namespacesShouldBeDefinedInRootScope = new Rule<JsName>() {
222 @Override
223 @Nullable
224 public JsName apply(@NotNull DeclarationDescriptor descriptor) {
225 if (!(descriptor instanceof NamespaceDescriptor)) {
226 return null;
227 }
228
229 String name = Namer.generateNamespaceName(descriptor);
230 return getRootScope().declareName(name);
231 }
232 };
233 Rule<JsName> memberDeclarationsInsideParentsScope = new Rule<JsName>() {
234 @Override
235 @Nullable
236 public JsName apply(@NotNull DeclarationDescriptor descriptor) {
237 JsScope scope = getEnclosingScope(descriptor);
238 DeclarationDescriptor declaration = descriptor.getContainingDeclaration();
239 if (!(descriptor instanceof FunctionDescriptor) || !(declaration instanceof ClassDescriptor)) {
240 return scope.declareFreshName(descriptor.getName().asString());
241 }
242
243 Collection<FunctionDescriptor> functions =
244 ((ClassDescriptor) declaration).getDefaultType().getMemberScope().getFunctions(descriptor.getName());
245 String name = descriptor.getName().asString();
246 int counter = -1;
247 if (functions.size() > 1) {
248 // see testOverloadedFun
249 FunctionDescriptor[] sorted = functions.toArray(new FunctionDescriptor[functions.size()]);
250 Arrays.sort(sorted, new Comparator<FunctionDescriptor>() {
251 @Override
252 public int compare(@NotNull FunctionDescriptor a, @NotNull FunctionDescriptor b) {
253 Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility());
254 if (result == null) {
255 return 0;
256 }
257 else if (result == 0) {
258 // open fun > not open fun
259 int aWeight = a.getModality().isOverridable() ? 1 : 0;
260 int bWeight = b.getModality().isOverridable() ? 1 : 0;
261 return bWeight - aWeight;
262 }
263
264 return result;
265 }
266 });
267 for (FunctionDescriptor function : sorted) {
268 if (function == descriptor) {
269 break;
270 }
271 counter++;
272 }
273 }
274
275 return scope.declareName(counter == -1 ? name : name + '_' + counter);
276 }
277 };
278 Rule<JsName> constructorHasTheSameNameAsTheClass = new Rule<JsName>() {
279 @Override
280 public JsName apply(@NotNull DeclarationDescriptor descriptor) {
281 if (!(descriptor instanceof ConstructorDescriptor)) {
282 return null;
283 }
284 ClassDescriptor containingClass = getContainingClass(descriptor);
285 assert containingClass != null : "Can't have constructor without a class";
286 return getNameForDescriptor(containingClass);
287 }
288 };
289 Rule<JsName> accessorsHasNamesWithSpecialPrefixes = new Rule<JsName>() {
290 @Override
291 public JsName apply(@NotNull DeclarationDescriptor descriptor) {
292 if (!(descriptor instanceof PropertyAccessorDescriptor)) {
293 return null;
294 }
295
296 PropertyAccessorDescriptor accessorDescriptor = (PropertyAccessorDescriptor) descriptor;
297 String propertyName = accessorDescriptor.getCorrespondingProperty().getName().asString();
298 if (isObjectDeclaration(accessorDescriptor.getCorrespondingProperty())) {
299 return declareName(descriptor, propertyName);
300 }
301
302 boolean isGetter = descriptor instanceof PropertyGetterDescriptor;
303 String accessorName = Namer.getNameForAccessor(propertyName, isGetter,
304 accessorDescriptor.getReceiverParameter() == null && isEcma5());
305 return declareName(descriptor, accessorName);
306 }
307 };
308
309 Rule<JsName> predefinedObjectsHasUnobfuscatableNames = new Rule<JsName>() {
310 @Override
311 public JsName apply(@NotNull DeclarationDescriptor descriptor) {
312 for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) {
313 if (!hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
314 continue;
315 }
316 String name = getNameForAnnotatedObject(descriptor, annotation);
317 name = (name != null) ? name : descriptor.getName().asString();
318 return getEnclosingScope(descriptor).declareName(name);
319 }
320 return null;
321 }
322 };
323 Rule<JsName> propertiesCorrespondToSpeciallyTreatedBackingFieldNames = new Rule<JsName>() {
324 @Override
325 public JsName apply(@NotNull DeclarationDescriptor descriptor) {
326 if (!(descriptor instanceof PropertyDescriptor)) {
327 return null;
328 }
329
330 String name = descriptor.getName().asString();
331 if (!isEcma5() || JsDescriptorUtils.isAsPrivate((PropertyDescriptor) descriptor)) {
332 name = Namer.getKotlinBackingFieldName(name);
333 }
334
335 return declareName(descriptor, name);
336 }
337 };
338 Rule<JsName> overridingDescriptorsReferToOriginalName = new Rule<JsName>() {
339 @Override
340 public JsName apply(@NotNull DeclarationDescriptor descriptor) {
341 //TODO: refactor
342 if (!(descriptor instanceof FunctionDescriptor)) {
343 return null;
344 }
345 FunctionDescriptor overriddenDescriptor = getOverriddenDescriptor((FunctionDescriptor) descriptor);
346 if (overriddenDescriptor == null) {
347 return null;
348 }
349
350 JsScope scope = getEnclosingScope(descriptor);
351 JsName result = getNameForDescriptor(overriddenDescriptor);
352 scope.declareName(result.getIdent());
353 return result;
354 }
355 };
356 addRule(namesForStandardClasses);
357 addRule(constructorHasTheSameNameAsTheClass);
358 addRule(predefinedObjectsHasUnobfuscatableNames);
359 addRule(propertiesCorrespondToSpeciallyTreatedBackingFieldNames);
360 addRule(namespacesShouldBeDefinedInRootScope);
361 addRule(overridingDescriptorsReferToOriginalName);
362 addRule(accessorsHasNamesWithSpecialPrefixes);
363 addRule(memberDeclarationsInsideParentsScope);
364 }
365 }
366
367 @NotNull
368 private JsScope getEnclosingScope(@NotNull DeclarationDescriptor descriptor) {
369 DeclarationDescriptor containingDeclaration = getContainingDeclaration(descriptor);
370 return getScopeForDescriptor(containingDeclaration.getOriginal());
371 }
372
373 private final class ScopeGenerator extends Generator<JsScope> {
374
375 public ScopeGenerator() {
376 Rule<JsScope> generateNewScopesForClassesWithNoAncestors = new Rule<JsScope>() {
377 @Override
378 public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
379 if (!(descriptor instanceof ClassDescriptor)) {
380 return null;
381 }
382 if (getSuperclass((ClassDescriptor) descriptor) == null) {
383 return getRootScope().innerScope("Scope for class " + descriptor.getName());
384 }
385 return null;
386 }
387 };
388 Rule<JsScope> generateInnerScopesForDerivedClasses = new Rule<JsScope>() {
389 @Override
390 public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
391 if (!(descriptor instanceof ClassDescriptor)) {
392 return null;
393 }
394 ClassDescriptor superclass = getSuperclass((ClassDescriptor) descriptor);
395 if (superclass == null) {
396 return null;
397 }
398 return getScopeForDescriptor(superclass).innerScope("Scope for class " + descriptor.getName());
399 }
400 };
401 Rule<JsScope> generateNewScopesForNamespaceDescriptors = new Rule<JsScope>() {
402 @Override
403 public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
404 if (!(descriptor instanceof NamespaceDescriptor)) {
405 return null;
406 }
407 return getRootScope().innerScope("Namespace " + descriptor.getName());
408 }
409 };
410 //TODO: never get there
411 Rule<JsScope> generateInnerScopesForMembers = new Rule<JsScope>() {
412 @Override
413 public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
414 JsScope enclosingScope = getEnclosingScope(descriptor);
415 return enclosingScope.innerScope("Scope for member " + descriptor.getName());
416 }
417 };
418 Rule<JsScope> createFunctionObjectsForCallableDescriptors = new Rule<JsScope>() {
419 @Override
420 public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
421 if (!(descriptor instanceof CallableDescriptor)) {
422 return null;
423 }
424 JsScope enclosingScope = getEnclosingScope(descriptor);
425
426 JsFunction correspondingFunction = JsAstUtils.createFunctionWithEmptyBody(enclosingScope);
427 assert (!scopeToFunction.containsKey(correspondingFunction.getScope())) : "Scope to function value overridden for " + descriptor;
428 scopeToFunction.put(correspondingFunction.getScope(), correspondingFunction);
429 return correspondingFunction.getScope();
430 }
431 };
432 addRule(createFunctionObjectsForCallableDescriptors);
433 addRule(generateNewScopesForClassesWithNoAncestors);
434 addRule(generateInnerScopesForDerivedClasses);
435 addRule(generateNewScopesForNamespaceDescriptors);
436 addRule(generateInnerScopesForMembers);
437 }
438 }
439
440 @Nullable
441 public JsNameRef getQualifierForDescriptor(@NotNull DeclarationDescriptor descriptor) {
442 if (qualifierIsNull.get(descriptor.getOriginal()) != null) {
443 return null;
444 }
445 return qualifiers.get(descriptor.getOriginal());
446 }
447
448 private final class QualifierGenerator extends Generator<JsNameRef> {
449 public QualifierGenerator() {
450 Rule<JsNameRef> standardObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
451 @Override
452 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
453 if (!standardClasses.isStandardObject(descriptor)) {
454 return null;
455 }
456 return namer.kotlinObject();
457 }
458 };
459 //TODO: review and refactor
460 Rule<JsNameRef> namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier = new Rule<JsNameRef>() {
461 @Override
462 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
463 DeclarationDescriptor containingDescriptor = getContainingDeclaration(descriptor);
464 if (!(containingDescriptor instanceof NamespaceDescriptor)) {
465 return null;
466 }
467
468 JsNameRef result = new JsNameRef(getNameForDescriptor(containingDescriptor));
469 if (DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) {
470 return result;
471 }
472
473 JsNameRef qualifier = result;
474 while ((containingDescriptor = getContainingDeclaration(containingDescriptor)) instanceof NamespaceDescriptor &&
475 !DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) {
476 JsNameRef ref = getNameForDescriptor(containingDescriptor).makeRef();
477 qualifier.setQualifier(ref);
478 qualifier = ref;
479 }
480
481 PsiElement element = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor);
482 if (element == null && descriptor instanceof PropertyAccessorDescriptor) {
483 element = BindingContextUtils.descriptorToDeclaration(bindingContext, ((PropertyAccessorDescriptor) descriptor)
484 .getCorrespondingProperty());
485 }
486
487 if (element != null) {
488 PsiFile file = element.getContainingFile();
489 String moduleName = file.getUserData(LibrarySourcesConfig.EXTERNAL_MODULE_NAME);
490 if (LibrarySourcesConfig.UNKNOWN_EXTERNAL_MODULE_NAME.equals(moduleName)) {
491 return null;
492 }
493 else if (moduleName != null) {
494 qualifier.setQualifier(new JsArrayAccess(namer.kotlin("modules"), program.getStringLiteral(moduleName)));
495 }
496 }
497
498 if (qualifier.getQualifier() == null) {
499 qualifier.setQualifier(new JsNameRef(Namer.getRootNamespaceName()));
500 }
501
502 return result;
503 }
504 };
505 Rule<JsNameRef> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() {
506 @Override
507 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
508 if (!(descriptor instanceof ConstructorDescriptor)) {
509 return null;
510 }
511 ClassDescriptor containingClass = getContainingClass(descriptor);
512 assert containingClass != null : "Can't have constructor without a class";
513 return getQualifierForDescriptor(containingClass);
514 }
515 };
516 Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
517 @Override
518 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
519 if (isLibraryObject(descriptor)) {
520 return namer.kotlinObject();
521 }
522 return null;
523 }
524 };
525 addRule(libraryObjectsHaveKotlinQualifier);
526 addRule(constructorHaveTheSameQualifierAsTheClass);
527 addRule(standardObjectsHaveKotlinQualifier);
528 addRule(namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier);
529 }
530 }
531
532 private static class QualifierIsNullGenerator extends Generator<Boolean> {
533
534 private QualifierIsNullGenerator() {
535 Rule<Boolean> propertiesHaveNoQualifiers = new Rule<Boolean>() {
536 @Override
537 public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
538 if (!(descriptor instanceof PropertyDescriptor)) {
539 return null;
540 }
541 return true;
542 }
543 };
544 //TODO: hack!
545 Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() {
546 @Override
547 public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
548 if (!AnnotationsUtils.isNativeObject(descriptor)) {
549 return null;
550 }
551 return true;
552 }
553 };
554 Rule<Boolean> topLevelNamespaceHaveNoQualifier = new Rule<Boolean>() {
555 @Override
556 public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
557 if (descriptor instanceof NamespaceDescriptor && DescriptorUtils.isRootNamespace((NamespaceDescriptor) descriptor)) {
558 return true;
559 }
560 return null;
561 }
562 };
563 addRule(topLevelNamespaceHaveNoQualifier);
564 addRule(propertiesHaveNoQualifiers);
565 addRule(nativeObjectsHaveNoQualifiers);
566 }
567 }
568 }