/* * Copyright 2010-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.js.translate.general; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.FunctionDescriptor; import org.jetbrains.kotlin.descriptors.ModuleDescriptor; import org.jetbrains.kotlin.idea.MainFunctionDetector; import org.jetbrains.kotlin.js.backend.ast.*; import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.facade.MainCallParameters; import org.jetbrains.kotlin.js.facade.TranslationUnit; import org.jetbrains.kotlin.js.facade.exceptions.TranslationException; import org.jetbrains.kotlin.js.facade.exceptions.TranslationRuntimeException; import org.jetbrains.kotlin.js.facade.exceptions.UnsupportedFeatureException; import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator; import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.StaticContext; import org.jetbrains.kotlin.js.translate.context.TemporaryVariable; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.declaration.FileDeclarationVisitor; import org.jetbrains.kotlin.js.translate.expression.ExpressionVisitor; import org.jetbrains.kotlin.js.translate.expression.PatternTranslator; import org.jetbrains.kotlin.js.translate.test.JSTestGenerator; import org.jetbrains.kotlin.js.translate.test.JSTester; import org.jetbrains.kotlin.js.translate.test.QUnitTester; import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils; import org.jetbrains.kotlin.js.translate.utils.BindingUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.mutator.AssignToExpressionMutator; import org.jetbrains.kotlin.psi.KtDeclaration; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtUnaryExpression; import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; import org.jetbrains.kotlin.resolve.constants.ConstantValue; import org.jetbrains.kotlin.resolve.constants.NullValue; import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator; import org.jetbrains.kotlin.serialization.js.ast.JsAstDeserializer; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.utils.ExceptionUtilsKt; import java.io.ByteArrayInputStream; import java.util.*; import static org.jetbrains.kotlin.js.translate.general.ModuleWrapperTranslation.wrapIfNecessary; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.convertToStatement; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.toStringLiteralList; import static org.jetbrains.kotlin.js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression; /** * This class provides a interface which all translators use to interact with each other. * Goal is to simplify interaction between translators. */ public final class Translation { private static final String ENUM_SIGNATURE = "kotlin$Enum"; private Translation() { } @NotNull public static PatternTranslator patternTranslator(@NotNull TranslationContext context) { return PatternTranslator.newInstance(context); } @NotNull public static JsNode translateExpression(@NotNull KtExpression expression, @NotNull TranslationContext context) { return translateExpression(expression, context, context.dynamicContext().jsBlock()); } @NotNull public static JsNode translateExpression(@NotNull KtExpression expression, @NotNull TranslationContext context, @NotNull JsBlock block) { JsExpression aliasForExpression = context.aliasingContext().getAliasForExpression(expression); if (aliasForExpression != null) { return aliasForExpression; } CompileTimeConstant<?> compileTimeValue = ConstantExpressionEvaluator.getConstant(expression, context.bindingContext()); if (compileTimeValue != null) { KotlinType type = context.bindingContext().getType(expression); if (type != null) { if (KotlinBuiltIns.isLong(type) || (KotlinBuiltIns.isInt(type) && expression instanceof KtUnaryExpression)) { JsExpression constantResult = translateConstant(compileTimeValue, expression, context); if (constantResult != null) return constantResult; } } } TranslationContext innerContext = context.innerBlock(); JsNode result = doTranslateExpression(expression, innerContext); context.moveVarsFrom(innerContext); block.getStatements().addAll(innerContext.dynamicContext().jsBlock().getStatements()); return result; } @Nullable public static JsExpression translateConstant( @NotNull CompileTimeConstant compileTimeValue, @NotNull KtExpression expression, @NotNull TranslationContext context ) { KotlinType expectedType = context.bindingContext().getType(expression); ConstantValue<?> constant = compileTimeValue.toConstantValue(expectedType != null ? expectedType : TypeUtils.NO_EXPECTED_TYPE); if (constant instanceof NullValue) { return JsLiteral.NULL; } Object value = constant.getValue(); if (value instanceof Integer || value instanceof Short || value instanceof Byte) { return context.program().getNumberLiteral(((Number) value).intValue()); } else if (value instanceof Long) { return JsAstUtils.newLong((Long) value, context); } else if (value instanceof Float) { float floatValue = (Float) value; double doubleValue; if (Float.isInfinite(floatValue) || Float.isNaN(floatValue)) { doubleValue = floatValue; } else { doubleValue = Double.parseDouble(Float.toString(floatValue)); } return context.program().getNumberLiteral(doubleValue); } else if (value instanceof Number) { return context.program().getNumberLiteral(((Number) value).doubleValue()); } else if (value instanceof Boolean) { return JsLiteral.getBoolean((Boolean) value); } //TODO: test if (value instanceof String) { return context.program().getStringLiteral((String) value); } if (value instanceof Character) { return context.program().getNumberLiteral(((Character) value).charValue()); } return null; } @NotNull private static JsNode doTranslateExpression(KtExpression expression, TranslationContext context) { try { return expression.accept(new ExpressionVisitor(), context); } catch (TranslationRuntimeException e) { throw e; } catch (RuntimeException | AssertionError e) { throw new TranslationRuntimeException(expression, e); } } @NotNull public static JsExpression translateAsExpression(@NotNull KtExpression expression, @NotNull TranslationContext context) { return translateAsExpression(expression, context, context.dynamicContext().jsBlock()); } @NotNull public static JsExpression translateAsExpression( @NotNull KtExpression expression, @NotNull TranslationContext context, @NotNull JsBlock block ) { JsNode jsNode = translateExpression(expression, context, block); if (jsNode instanceof JsExpression) { KotlinType expressionType = context.bindingContext().getType(expression); return unboxIfNeeded((JsExpression) jsNode, expressionType != null && KotlinBuiltIns.isCharOrNullableChar(expressionType)); } assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString(); if (BindingContextUtilsKt.isUsedAsExpression(expression, context.bindingContext())) { TemporaryVariable result = context.declareTemporary(null); AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference()); block.getStatements().add(mutateLastExpression(jsNode, saveResultToTemporaryMutator)); return result.reference(); } block.getStatements().add(convertToStatement(jsNode)); return JsLiteral.NULL; } @NotNull public static JsExpression unboxIfNeeded(@NotNull JsExpression expression, boolean charOrNullableChar) { if (charOrNullableChar && (expression instanceof JsInvocation || expression instanceof JsNameRef || expression instanceof JsArrayAccess) ) { expression = JsAstUtils.boxedCharToChar(expression); } return expression; } @NotNull public static JsStatement translateAsStatement(@NotNull KtExpression expression, @NotNull TranslationContext context) { return translateAsStatement(expression, context, context.dynamicContext().jsBlock()); } @NotNull public static JsStatement translateAsStatement( @NotNull KtExpression expression, @NotNull TranslationContext context, @NotNull JsBlock block) { return convertToStatement(translateExpression(expression, context, block)); } @NotNull public static JsStatement translateAsStatementAndMergeInBlockIfNeeded( @NotNull KtExpression expression, @NotNull TranslationContext context ) { JsBlock block = new JsBlock(); JsNode node = translateExpression(expression, context, block); return JsAstUtils.mergeStatementInBlockIfNeeded(convertToStatement(node), block); } @NotNull public static AstGenerationResult generateAst( @NotNull BindingTrace bindingTrace, @NotNull Collection<TranslationUnit> units, @NotNull MainCallParameters mainCallParameters, @NotNull ModuleDescriptor moduleDescriptor, @NotNull JsConfig config ) throws TranslationException { try { return doGenerateAst(bindingTrace, units, mainCallParameters, moduleDescriptor, config); } catch (UnsupportedOperationException e) { throw new UnsupportedFeatureException("Unsupported feature used.", e); } catch (Throwable e) { throw ExceptionUtilsKt.rethrow(e); } } @NotNull private static AstGenerationResult doGenerateAst( @NotNull BindingTrace bindingTrace, @NotNull Collection<TranslationUnit> units, @NotNull MainCallParameters mainCallParameters, @NotNull ModuleDescriptor moduleDescriptor, @NotNull JsConfig config ) { JsProgram program = new JsProgram(); JsFunction rootFunction = new JsFunction(program.getRootScope(), new JsBlock(), "root function"); JsName internalModuleName = program.getScope().declareName("_"); Merger merger = new Merger(rootFunction, internalModuleName, moduleDescriptor); Map<KtFile, JsProgramFragment> fragmentMap = new HashMap<>(); List<JsProgramFragment> fragments = new ArrayList<>(); List<JsProgramFragment> newFragments = new ArrayList<>(); Map<KtFile, List<DeclarationDescriptor>> fileMemberScopes = new HashMap<>(); JsAstDeserializer deserializer = new JsAstDeserializer(program); for (TranslationUnit unit : units) { if (unit instanceof TranslationUnit.SourceFile) { KtFile file = ((TranslationUnit.SourceFile) unit).getFile(); StaticContext staticContext = new StaticContext(bindingTrace, config, moduleDescriptor); TranslationContext context = TranslationContext.rootContext(staticContext); List<DeclarationDescriptor> fileMemberScope = new ArrayList<>(); translateFile(context, file, fileMemberScope); fragments.add(staticContext.getFragment()); newFragments.add(staticContext.getFragment()); fragmentMap.put(file, staticContext.getFragment()); fileMemberScopes.put(file, fileMemberScope); merger.addFragment(staticContext.getFragment()); } else if (unit instanceof TranslationUnit.BinaryAst) { byte[] astData = ((TranslationUnit.BinaryAst) unit).getData(); JsProgramFragment fragment = deserializer.deserialize(new ByteArrayInputStream(astData)); merger.addFragment(fragment); fragments.add(fragment); } } JsProgramFragment testFragment = mayBeGenerateTests(config, bindingTrace, moduleDescriptor); fragments.add(testFragment); newFragments.add(testFragment); merger.addFragment(testFragment); rootFunction.getParameters().add(new JsParameter(internalModuleName)); if (mainCallParameters.shouldBeGenerated()) { JsProgramFragment mainCallFragment = generateCallToMain( bindingTrace, config, moduleDescriptor, mainCallParameters.arguments()); if (mainCallFragment != null) { fragments.add(mainCallFragment); newFragments.add(mainCallFragment); merger.addFragment(mainCallFragment); } } merger.merge(); JsBlock rootBlock = rootFunction.getBody(); List<JsStatement> statements = rootBlock.getStatements(); statements.add(0, program.getStringLiteral("use strict").makeStmt()); if (!isBuiltinModule(fragments)) { defineModule(program, statements, config.getModuleId()); } // Invoke function passing modules as arguments // This should help minifier tool to recognize references to these modules as local variables and make them shorter. List<JsImportedModule> importedModuleList = merger.getImportedModules(); for (JsImportedModule importedModule : importedModuleList) { rootFunction.getParameters().add(new JsParameter(importedModule.getInternalName())); } statements.add(new JsReturn(internalModuleName.makeRef())); JsBlock block = program.getGlobalBlock(); block.getStatements().addAll(wrapIfNecessary(config.getModuleId(), rootFunction, importedModuleList, program, config.getModuleKind())); return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, newFragments, fileMemberScopes, importedModuleList); } private static boolean isBuiltinModule(@NotNull List<JsProgramFragment> fragments) { for (JsProgramFragment fragment : fragments) { for (JsNameBinding nameBinding : fragment.getNameBindings()) { if (nameBinding.getKey().equals(ENUM_SIGNATURE) && !fragment.getImports().containsKey(ENUM_SIGNATURE)) { return true; } } } return false; } private static void translateFile( @NotNull TranslationContext context, @NotNull KtFile file, @NotNull List<DeclarationDescriptor> fileMemberScope ) { FileDeclarationVisitor fileVisitor = new FileDeclarationVisitor(context); try { for (KtDeclaration declaration : file.getDeclarations()) { DeclarationDescriptor descriptor = BindingUtils.getDescriptorForElement(context.bindingContext(), declaration); fileMemberScope.add(descriptor); if (!AnnotationsUtils.isPredefinedObject(descriptor)) { declaration.accept(fileVisitor, context); } } } catch (TranslationRuntimeException e) { throw e; } catch (RuntimeException | AssertionError e) { throw new TranslationRuntimeException(file, e); } } private static void defineModule(@NotNull JsProgram program, @NotNull List<JsStatement> statements, @NotNull String moduleId) { JsName rootPackageName = program.getScope().findName(Namer.getRootPackageName()); if (rootPackageName != null) { Namer namer = Namer.newInstance(program.getScope()); statements.add(new JsInvocation(namer.kotlin("defineModule"), program.getStringLiteral(moduleId), rootPackageName.makeRef()).makeStmt()); } } @NotNull private static JsProgramFragment mayBeGenerateTests( @NotNull JsConfig config, @NotNull BindingTrace trace, @NotNull ModuleDescriptor moduleDescriptor ) { StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor); TranslationContext context = TranslationContext.rootContext(staticContext); JSTester tester = new QUnitTester(context); JSTestGenerator.generateTestCalls(context, moduleDescriptor, tester); return staticContext.getFragment(); } //TODO: determine whether should throw exception @Nullable private static JsProgramFragment generateCallToMain( @NotNull BindingTrace trace, @NotNull JsConfig config, @NotNull ModuleDescriptor moduleDescriptor, @NotNull List<String> arguments ) { StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor); TranslationContext context = TranslationContext.rootContext(staticContext); MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(context.bindingContext()); FunctionDescriptor functionDescriptor = mainFunctionDetector.getMainFunction(moduleDescriptor); if (functionDescriptor == null) { return null; } JsArrayLiteral argument = new JsArrayLiteral(toStringLiteralList(arguments, context.program())); JsExpression call = CallTranslator.INSTANCE.buildCall(context, functionDescriptor, Collections.singletonList(argument), null); context.addTopLevelStatement(call.makeStmt()); return staticContext.getFragment(); } }