001 /*
002 * Copyright 2010-2016 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.kotlin.js.translate.context;
018
019 import com.google.dart.compiler.backend.js.ast.*;
020 import com.intellij.psi.PsiElement;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.annotations.Nullable;
023 import org.jetbrains.kotlin.descriptors.*;
024 import org.jetbrains.kotlin.js.config.JsConfig;
025 import org.jetbrains.kotlin.js.translate.intrinsic.Intrinsics;
026 import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
027 import org.jetbrains.kotlin.name.FqName;
028 import org.jetbrains.kotlin.psi.KtExpression;
029 import org.jetbrains.kotlin.resolve.BindingContext;
030 import org.jetbrains.kotlin.resolve.BindingTrace;
031 import org.jetbrains.kotlin.resolve.DescriptorUtils;
032 import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
033
034 import java.util.*;
035
036 import static org.jetbrains.kotlin.js.translate.context.UsageTrackerKt.getNameForCapturedDescriptor;
037 import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getDescriptorForElement;
038
039 /**
040 * All the info about the state of the translation process.
041 */
042 public class TranslationContext {
043 @NotNull
044 private final DynamicContext dynamicContext;
045 @NotNull
046 private final StaticContext staticContext;
047 @NotNull
048 private final AliasingContext aliasingContext;
049 @Nullable
050 private final UsageTracker usageTracker;
051 @Nullable
052 private final TranslationContext parent;
053 @Nullable
054 private final DefinitionPlace definitionPlace;
055 @Nullable
056 private final DeclarationDescriptor declarationDescriptor;
057 @Nullable
058 private final ClassDescriptor classDescriptor;
059
060 @NotNull
061 public static TranslationContext rootContext(@NotNull StaticContext staticContext, JsFunction rootFunction) {
062 DynamicContext rootDynamicContext = DynamicContext.rootContext(rootFunction.getScope(), rootFunction.getBody());
063 AliasingContext rootAliasingContext = AliasingContext.getCleanContext();
064 return new TranslationContext(null, staticContext, rootDynamicContext, rootAliasingContext, null, null, null);
065 }
066
067 private final Map<JsExpression, TemporaryConstVariable> expressionToTempConstVariableCache = new HashMap<JsExpression, TemporaryConstVariable>();
068
069 private TranslationContext(
070 @Nullable TranslationContext parent,
071 @NotNull StaticContext staticContext,
072 @NotNull DynamicContext dynamicContext,
073 @NotNull AliasingContext aliasingContext,
074 @Nullable UsageTracker usageTracker,
075 @Nullable DefinitionPlace definitionPlace,
076 @Nullable DeclarationDescriptor declarationDescriptor
077 ) {
078 this.parent = parent;
079 this.dynamicContext = dynamicContext;
080 this.staticContext = staticContext;
081 this.aliasingContext = aliasingContext;
082 this.usageTracker = usageTracker;
083 this.definitionPlace = definitionPlace;
084 this.declarationDescriptor = declarationDescriptor;
085 if (declarationDescriptor instanceof ClassDescriptor) {
086 this.classDescriptor = (ClassDescriptor) declarationDescriptor;
087 }
088 else {
089 this.classDescriptor = parent != null ? parent.classDescriptor : null;
090 }
091 }
092
093 @NotNull
094 public Map<String, JsName> getImportedModules() {
095 return staticContext.getImportedModules();
096 }
097
098 @Nullable
099 public UsageTracker usageTracker() {
100 return usageTracker;
101 }
102
103 @NotNull
104 public DynamicContext dynamicContext() {
105 return dynamicContext;
106 }
107
108 @NotNull
109 public TranslationContext contextWithScope(@NotNull JsFunction fun) {
110 return this.newFunctionBody(fun, aliasingContext, declarationDescriptor);
111 }
112
113 @NotNull
114 public TranslationContext newFunctionBody(@NotNull JsFunction fun, @Nullable AliasingContext aliasingContext,
115 DeclarationDescriptor descriptor) {
116 DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody());
117 if (aliasingContext == null) {
118 aliasingContext = this.aliasingContext.inner();
119 }
120
121 return new TranslationContext(this, this.staticContext, dynamicContext, aliasingContext, this.usageTracker, null, descriptor);
122 }
123
124 @NotNull
125 public TranslationContext newFunctionBodyWithUsageTracker(@NotNull JsFunction fun, @NotNull MemberDescriptor descriptor) {
126 DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody());
127 UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, fun.getScope());
128 return new TranslationContext(this, this.staticContext, dynamicContext, this.aliasingContext.inner(), usageTracker,
129 this.definitionPlace, descriptor);
130 }
131
132 @NotNull
133 public TranslationContext innerWithUsageTracker(@NotNull JsScope scope, @NotNull MemberDescriptor descriptor) {
134 UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, scope);
135 return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, definitionPlace,
136 descriptor);
137 }
138
139 @NotNull
140 public TranslationContext innerBlock(@NotNull JsBlock block) {
141 return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker, null,
142 this.declarationDescriptor);
143 }
144
145 @NotNull
146 public TranslationContext innerBlock() {
147 return innerBlock(new JsBlock());
148 }
149
150 @NotNull
151 public TranslationContext newDeclaration(@NotNull DeclarationDescriptor descriptor, @Nullable DefinitionPlace place) {
152 DynamicContext dynamicContext = DynamicContext.newContext(getScopeForDescriptor(descriptor), getBlockForDescriptor(descriptor));
153 return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, place, descriptor);
154 }
155
156 @NotNull
157 private TranslationContext innerWithAliasingContext(AliasingContext aliasingContext) {
158 return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, null, declarationDescriptor);
159 }
160
161 @NotNull
162 public TranslationContext innerContextWithAliased(@NotNull DeclarationDescriptor correspondingDescriptor, @NotNull JsExpression alias) {
163 return this.innerWithAliasingContext(aliasingContext.inner(correspondingDescriptor, alias));
164 }
165
166 @NotNull
167 public TranslationContext innerContextWithAliasesForExpressions(@NotNull Map<KtExpression, JsExpression> aliases) {
168 return this.innerWithAliasingContext(aliasingContext.withExpressionsAliased(aliases));
169 }
170
171 @NotNull
172 public TranslationContext innerContextWithDescriptorsAliased(@NotNull Map<DeclarationDescriptor, JsExpression> aliases) {
173 return this.innerWithAliasingContext(aliasingContext.withDescriptorsAliased(aliases));
174 }
175
176 @NotNull
177 private JsBlock getBlockForDescriptor(@NotNull DeclarationDescriptor descriptor) {
178 if (descriptor instanceof CallableDescriptor) {
179 return getFunctionObject((CallableDescriptor) descriptor).getBody();
180 }
181 else {
182 return new JsBlock();
183 }
184 }
185
186 @NotNull
187 public BindingContext bindingContext() {
188 return staticContext.getBindingContext();
189 }
190
191 @NotNull
192 public BindingTrace bindingTrace() {
193 return staticContext.getBindingTrace();
194 }
195
196 @NotNull
197 public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
198 return staticContext.getScopeForDescriptor(descriptor);
199 }
200
201 @NotNull
202 public JsName getNameForElement(@NotNull PsiElement element) {
203 DeclarationDescriptor descriptor = getDescriptorForElement(bindingContext(), element);
204 return getNameForDescriptor(descriptor);
205 }
206
207 @NotNull
208 public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
209 return staticContext.getNameForDescriptor(descriptor);
210 }
211
212 @NotNull
213 public JsName getNameForPackage(@NotNull FqName fqName) {
214 return staticContext.getNameForPackage(fqName);
215 }
216
217 @NotNull
218 public JsName declarePropertyOrPropertyAccessorName(@NotNull DeclarationDescriptor descriptor, @NotNull String name, boolean fresh) {
219 return staticContext.declarePropertyOrPropertyAccessorName(descriptor, name, fresh);
220 }
221
222 @NotNull
223 public JsNameRef getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
224 return staticContext.getQualifiedReference(descriptor);
225 }
226
227 @NotNull
228 public JsNameRef getQualifiedReference(@NotNull FqName packageFqName) {
229 return staticContext.getQualifiedReference(packageFqName);
230 }
231
232 @NotNull
233 public JsName getNameForBackingField(@NotNull PropertyDescriptor property) {
234 return staticContext.getNameForBackingField(property);
235 }
236
237 @NotNull
238 public TemporaryVariable declareTemporary(@Nullable JsExpression initExpression) {
239 return dynamicContext.declareTemporary(initExpression);
240 }
241
242 @NotNull
243 public JsExpression defineTemporary(@NotNull JsExpression initExpression) {
244 TemporaryVariable var = dynamicContext.declareTemporary(initExpression);
245 addStatementToCurrentBlock(var.assignmentStatement());
246 return var.reference();
247 }
248
249 @NotNull
250 public JsExpression cacheExpressionIfNeeded(@NotNull JsExpression expression) {
251 return TranslationUtils.isCacheNeeded(expression) ? defineTemporary(expression) : expression;
252 }
253
254 @NotNull
255 public TemporaryConstVariable getOrDeclareTemporaryConstVariable(@NotNull JsExpression expression) {
256 TemporaryConstVariable tempVar = expressionToTempConstVariableCache.get(expression);
257
258 if (tempVar == null) {
259 TemporaryVariable tmpVar = declareTemporary(expression);
260
261 tempVar = new TemporaryConstVariable(tmpVar.name(), tmpVar.assignmentExpression());
262
263 expressionToTempConstVariableCache.put(expression, tempVar);
264 expressionToTempConstVariableCache.put(tmpVar.assignmentExpression(), tempVar);
265 }
266
267 return tempVar;
268 }
269
270 public void associateExpressionToLazyValue(JsExpression expression, TemporaryConstVariable temporaryConstVariable) {
271 assert expression == temporaryConstVariable.assignmentExpression();
272 expressionToTempConstVariableCache.put(expression, temporaryConstVariable);
273 }
274
275 @NotNull
276 public Namer namer() {
277 return staticContext.getNamer();
278 }
279
280 @NotNull
281 public Intrinsics intrinsics() {
282 return staticContext.getIntrinsics();
283 }
284
285 @NotNull
286 public JsProgram program() {
287 return staticContext.getProgram();
288 }
289
290 @NotNull
291 public JsConfig getConfig() {
292 return staticContext.getConfig();
293 }
294
295 @NotNull
296 public JsScope scope() {
297 return dynamicContext.getScope();
298 }
299
300 @NotNull
301 public AliasingContext aliasingContext() {
302 return aliasingContext;
303 }
304
305 @NotNull
306 public JsFunction getFunctionObject(@NotNull CallableDescriptor descriptor) {
307 return staticContext.getFunctionWithScope(descriptor);
308 }
309
310 public void addStatementToCurrentBlock(@NotNull JsStatement statement) {
311 dynamicContext.jsBlock().getStatements().add(statement);
312 }
313
314 public void addStatementsToCurrentBlock(@NotNull Collection<JsStatement> statements) {
315 dynamicContext.jsBlock().getStatements().addAll(statements);
316 }
317
318 public void addStatementsToCurrentBlockFrom(@NotNull TranslationContext context) {
319 addStatementsToCurrentBlockFrom(context.dynamicContext().jsBlock());
320 }
321
322 public void addStatementsToCurrentBlockFrom(@NotNull JsBlock block) {
323 dynamicContext.jsBlock().getStatements().addAll(block.getStatements());
324 }
325
326 public boolean currentBlockIsEmpty() {
327 return dynamicContext.jsBlock().isEmpty();
328 }
329
330 public void moveVarsFrom(@NotNull TranslationContext context) {
331 dynamicContext.moveVarsFrom(context.dynamicContext());
332 }
333
334 @NotNull
335 public JsBlock getCurrentBlock() {
336 return dynamicContext.jsBlock();
337 }
338
339 @Nullable
340 public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
341 JsNameRef nameRef = captureIfNeedAndGetCapturedName(descriptor);
342 if (nameRef != null) {
343 return nameRef;
344 }
345
346 return aliasingContext.getAliasForDescriptor(descriptor);
347 }
348
349 @NotNull
350 public JsExpression getDispatchReceiver(@NotNull ReceiverParameterDescriptor descriptor) {
351 JsExpression alias = getAliasForDescriptor(descriptor);
352 if (alias != null) {
353 return alias;
354 }
355 if (DescriptorUtils.isObject(descriptor.getContainingDeclaration())) {
356 if (isConstructorOrDirectScope(descriptor.getContainingDeclaration())) {
357 return JsLiteral.THIS;
358 }
359 else {
360 return getQualifiedReference(descriptor.getContainingDeclaration());
361 }
362 }
363
364 if (descriptor.getValue() instanceof ExtensionReceiver) return JsLiteral.THIS;
365
366 ClassifierDescriptor classifier = descriptor.getValue().getType().getConstructor().getDeclarationDescriptor();
367
368 // TODO: can't tell why this assertion is valid, revisit this code later
369 assert classifier instanceof ClassDescriptor;
370
371 ClassDescriptor cls = (ClassDescriptor) classifier;
372
373 assert classDescriptor != null : "Can't get ReceiverParameterDescriptor in top level";
374 JsExpression receiver = getAliasForDescriptor(classDescriptor.getThisAsReceiverParameter());
375 if (receiver == null) {
376 receiver = JsLiteral.THIS;
377 }
378
379 return getDispatchReceiverPath(cls, receiver);
380 }
381
382 private boolean isConstructorOrDirectScope(DeclarationDescriptor descriptor) {
383 if (declarationDescriptor instanceof ClassDescriptor && !DescriptorUtils.isCompanionObject(declarationDescriptor)) {
384 return descriptor == declarationDescriptor;
385 }
386 else {
387 return declarationDescriptor != null && descriptor == DescriptorUtils.getContainingClass(declarationDescriptor);
388 }
389 }
390
391 @NotNull
392 private JsExpression getDispatchReceiverPath(@Nullable ClassDescriptor cls, JsExpression thisExpression) {
393 if (cls != null) {
394 JsExpression alias = getAliasForDescriptor(cls);
395 if (alias != null) {
396 return alias;
397 }
398 }
399
400 if (classDescriptor == cls || parent == null) {
401 return thisExpression;
402 }
403
404 ClassDescriptor parentDescriptor = parent.classDescriptor;
405 if (classDescriptor != parentDescriptor) {
406 return new JsNameRef(Namer.OUTER_FIELD_NAME, parent.getDispatchReceiverPath(cls, thisExpression));
407 }
408 else {
409 return parent.getDispatchReceiverPath(cls, thisExpression);
410 }
411 }
412
413 @NotNull
414 public DefinitionPlace getDefinitionPlace() {
415 if (definitionPlace != null) return definitionPlace;
416 if (parent != null) return parent.getDefinitionPlace();
417
418 throw new AssertionError("Can not find definition place from rootContext(definitionPlace and parent is null)");
419 }
420
421 @NotNull
422 public JsNameRef define(DeclarationDescriptor descriptor, JsExpression expression) {
423 String suggestedName = TranslationUtils.getSuggestedNameForInnerDeclaration(staticContext, descriptor);
424 return getDefinitionPlace().define(suggestedName, expression);
425 }
426
427 @Nullable
428 private JsNameRef captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
429 if (usageTracker != null) {
430 usageTracker.used(descriptor);
431
432 JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
433 if (name != null) {
434 JsNameRef result = name.makeRef();
435 if (shouldCaptureViaThis()) {
436 result.setQualifier(JsLiteral.THIS);
437 }
438 return result;
439 }
440 }
441
442 return null;
443 }
444
445 private boolean shouldCaptureViaThis() {
446 if (declarationDescriptor == null) return false;
447
448 if (DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor)) return false;
449 if (declarationDescriptor instanceof ConstructorDescriptor &&
450 DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor.getContainingDeclaration())) return false;
451
452 return true;
453 }
454
455 @Nullable
456 public DeclarationDescriptor getDeclarationDescriptor() {
457 return declarationDescriptor;
458 }
459
460 public void putClassOrConstructorClosure(@NotNull MemberDescriptor descriptor, @NotNull List<DeclarationDescriptor> closure) {
461 staticContext.putClassOrConstructorClosure(descriptor, closure);
462 }
463
464 @Nullable
465 public List<DeclarationDescriptor> getClassOrConstructorClosure(@NotNull MemberDescriptor classOrConstructor) {
466 List<DeclarationDescriptor> result = staticContext.getClassOrConstructorClosure(classOrConstructor);
467 if (result == null &&
468 classOrConstructor instanceof ConstructorDescriptor &&
469 ((ConstructorDescriptor) classOrConstructor).isPrimary()
470 ) {
471 result = staticContext.getClassOrConstructorClosure((ClassDescriptor) classOrConstructor.getContainingDeclaration());
472 }
473 return result;
474 }
475
476 /**
477 * Gets an expression to pass to a constructor of a closure function. I.e. consider the case:
478 *
479 * ```
480 * fun a(x) {
481 * fun b(y) = x + y
482 * return b
483 * }
484 * ```
485 *
486 * Here, `x` is a free variable of `b`. Transform `a` into the following form:
487 *
488 * ```
489 * fun a(x) {
490 * fun b0(x0) = { y -> x0 * y }
491 * return b0(x)
492 * }
493 * ```
494 *
495 * This function generates arguments passed to newly generated `b0` closure, as well as for the similar case of local class and
496 * object expression.
497 *
498 * @param descriptor represents a free variable or, more generally, free declaration.
499 * @return expression to pass to a closure constructor.
500 */
501 @NotNull
502 public JsExpression getArgumentForClosureConstructor(@NotNull DeclarationDescriptor descriptor) {
503 JsExpression alias = getAliasForDescriptor(descriptor);
504 if (alias != null) return alias;
505 if (descriptor instanceof ReceiverParameterDescriptor) {
506 return getDispatchReceiver((ReceiverParameterDescriptor) descriptor);
507 }
508 return getNameForDescriptor(descriptor).makeRef();
509 }
510
511 @Nullable
512 public JsName getOuterClassReference(ClassDescriptor descriptor) {
513 DeclarationDescriptor container = descriptor.getContainingDeclaration();
514 if (!(container instanceof ClassDescriptor) || !descriptor.isInner()) {
515 return null;
516 }
517
518 return staticContext.getScopeForDescriptor(descriptor).declareName(Namer.OUTER_FIELD_NAME);
519 }
520
521 public void startDeclaration() {
522 ClassDescriptor classDescriptor = this.classDescriptor;
523 if (classDescriptor != null && !(classDescriptor.getContainingDeclaration() instanceof ClassOrPackageFragmentDescriptor)) {
524 staticContext.getDeferredCallSites().put(classDescriptor, new ArrayList<DeferredCallSite>());
525 }
526 }
527
528 @NotNull
529 public List<DeferredCallSite> endDeclaration() {
530 List<DeferredCallSite> result = null;
531 if (classDescriptor != null) {
532 result = staticContext.getDeferredCallSites().remove(classDescriptor);
533 }
534 if (result == null) {
535 result = Collections.emptyList();
536 }
537 return result;
538 }
539
540 public boolean shouldBeDeferred(@NotNull ConstructorDescriptor constructor) {
541 ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
542 return staticContext.getDeferredCallSites().containsKey(classDescriptor);
543 }
544
545 public void deferConstructorCall(@NotNull ConstructorDescriptor constructor, @NotNull List<JsExpression> invocationArgs) {
546 ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
547 List<DeferredCallSite> callSites = staticContext.getDeferredCallSites().get(classDescriptor);
548 if (callSites == null) throw new IllegalStateException("This method should be call only when `shouldBeDeferred` method " +
549 "reports true for given constructor: " + constructor);
550 callSites.add(new DeferredCallSite(constructor, invocationArgs, this));
551 }
552
553 @Nullable
554 public JsExpression getModuleExpressionFor(@NotNull DeclarationDescriptor descriptor) {
555 return staticContext.getModuleExpressionFor(descriptor);
556 }
557 }