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.expression.LiteralFunctionTranslator;
034 import org.jetbrains.k2js.translate.intrinsic.Intrinsics;
035 import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
036 import org.jetbrains.k2js.translate.utils.JsAstUtils;
037 import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
038 import org.jetbrains.k2js.translate.utils.PredefinedAnnotation;
039
040 import java.util.Map;
041
042 import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.*;
043 import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
044 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
045
046 /**
047 * Aggregates all the static parts of the context.
048 */
049 public 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 }
445
446 if (qualifier.getQualifier() == null) {
447 qualifier.setQualifier(new JsNameRef(Namer.getRootNamespaceName()));
448 }
449
450 return result;
451 }
452 };
453 Rule<JsNameRef> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() {
454 @Override
455 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
456 if (!(descriptor instanceof ConstructorDescriptor)) {
457 return null;
458 }
459 ClassDescriptor containingClass = getContainingClass(descriptor);
460 assert containingClass != null : "Can't have constructor without a class";
461 return getQualifierForDescriptor(containingClass);
462 }
463 };
464 Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
465 @Override
466 public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
467 if (isLibraryObject(descriptor)) {
468 return namer.kotlinObject();
469 }
470 return null;
471 }
472 };
473 addRule(libraryObjectsHaveKotlinQualifier);
474 addRule(constructorHaveTheSameQualifierAsTheClass);
475 addRule(standardObjectsHaveKotlinQualifier);
476 addRule(namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier);
477 }
478 }
479
480 private static class QualifierIsNullGenerator extends Generator<Boolean> {
481
482 private QualifierIsNullGenerator() {
483 Rule<Boolean> propertiesHaveNoQualifiers = new Rule<Boolean>() {
484 @Override
485 public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
486 if (!(descriptor instanceof PropertyDescriptor)) {
487 return null;
488 }
489 return true;
490 }
491 };
492 //TODO: hack!
493 Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() {
494 @Override
495 public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
496 if (!AnnotationsUtils.isNativeObject(descriptor)) {
497 return null;
498 }
499 return true;
500 }
501 };
502 Rule<Boolean> topLevelNamespaceHaveNoQualifier = new Rule<Boolean>() {
503 @Override
504 public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
505 if (descriptor instanceof NamespaceDescriptor && DescriptorUtils.isRootNamespace((NamespaceDescriptor) descriptor)) {
506 return true;
507 }
508 return null;
509 }
510 };
511 addRule(topLevelNamespaceHaveNoQualifier);
512 addRule(propertiesHaveNoQualifiers);
513 addRule(nativeObjectsHaveNoQualifiers);
514 }
515 }
516 }