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.initializer; 018 019 import com.google.dart.compiler.backend.js.ast.*; 020 import com.intellij.util.SmartList; 021 import org.jetbrains.annotations.NotNull; 022 import org.jetbrains.annotations.Nullable; 023 import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor; 024 import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; 025 import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; 026 import org.jetbrains.jet.lang.psi.JetClassOrObject; 027 import org.jetbrains.jet.lang.psi.JetDelegationSpecifier; 028 import org.jetbrains.jet.lang.psi.JetDelegatorToSuperCall; 029 import org.jetbrains.jet.lang.psi.JetParameter; 030 import org.jetbrains.jet.lang.resolve.BindingContext; 031 import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; 032 import org.jetbrains.jet.lang.types.JetType; 033 import org.jetbrains.jet.lexer.JetTokens; 034 import org.jetbrains.k2js.translate.context.Namer; 035 import org.jetbrains.k2js.translate.context.TranslationContext; 036 import org.jetbrains.k2js.translate.general.AbstractTranslator; 037 import org.jetbrains.k2js.translate.reference.CallArgumentTranslator; 038 039 import java.util.ArrayList; 040 import java.util.Collections; 041 import java.util.List; 042 043 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType; 044 import static org.jetbrains.k2js.translate.utils.BindingUtils.*; 045 import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments; 046 import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters; 047 048 public final class ClassInitializerTranslator extends AbstractTranslator { 049 @NotNull 050 private final JetClassOrObject classDeclaration; 051 @NotNull 052 private final List<JsStatement> initializerStatements = new SmartList<JsStatement>(); 053 054 public ClassInitializerTranslator( 055 @NotNull JetClassOrObject classDeclaration, 056 @NotNull TranslationContext context 057 ) { 058 // Note: it's important we use scope for class descriptor because anonymous function used in property initializers 059 // belong to the properties themselves 060 super(context.newDeclaration(getConstructor(context.bindingContext(), classDeclaration), null)); 061 this.classDeclaration = classDeclaration; 062 } 063 064 @NotNull 065 public JsFunction generateInitializeMethod() { 066 //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way 067 ConstructorDescriptor primaryConstructor = getConstructor(bindingContext(), classDeclaration); 068 JsFunction result = context().getFunctionObject(primaryConstructor); 069 //NOTE: while we translate constructor parameters we also add property initializer statements 070 // for properties declared as constructor parameters 071 result.getParameters().addAll(translatePrimaryConstructorParameters()); 072 mayBeAddCallToSuperMethod(result); 073 new InitializerVisitor(initializerStatements).traverseContainer(classDeclaration, context()); 074 075 List<JsStatement> statements = result.getBody().getStatements(); 076 statements.addAll(setDefaultValueForArguments(primaryConstructor, context())); 077 for (JsStatement statement : initializerStatements) { 078 if (statement instanceof JsBlock) { 079 statements.addAll(((JsBlock) statement).getStatements()); 080 } 081 else { 082 statements.add(statement); 083 } 084 } 085 086 return result; 087 } 088 089 @NotNull 090 public JsExpression generateEnumEntryInstanceCreation(@NotNull JetType enumClassType) { 091 JetDelegatorToSuperCall superCall = getSuperCall(); 092 List<JsExpression> arguments; 093 if (superCall != null) { 094 arguments = translateArguments(superCall); 095 } else { 096 arguments = Collections.emptyList(); 097 } 098 JsNameRef reference = context().getQualifiedReference(getClassDescriptorForType(enumClassType)); 099 return new JsNew(reference, arguments); 100 } 101 102 private void mayBeAddCallToSuperMethod(JsFunction initializer) { 103 if (classDeclaration.hasModifier(JetTokens.ENUM_KEYWORD)) { 104 addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer); 105 return; 106 } 107 if (hasAncestorClass(bindingContext(), classDeclaration)) { 108 JetDelegatorToSuperCall superCall = getSuperCall(); 109 if (superCall == null) { 110 return; 111 } 112 addCallToSuperMethod(translateArguments(superCall), initializer); 113 } 114 } 115 116 private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, JsFunction initializer) { 117 JsName ref = context().scope().declareName(Namer.CALLEE_NAME); 118 initializer.setName(ref); 119 JsInvocation call = new JsInvocation(Namer.getFunctionCallRef(Namer.superMethodNameRef(ref))); 120 call.getArguments().add(JsLiteral.THIS); 121 call.getArguments().addAll(arguments); 122 initializerStatements.add(0, call.makeStmt()); 123 } 124 125 @NotNull 126 private List<JsExpression> translateArguments(@NotNull JetDelegatorToSuperCall superCall) { 127 ResolvedCall<?> call = context().bindingContext().get(BindingContext.RESOLVED_CALL, superCall.getCalleeExpression()); 128 assert call != null : "ResolvedCall for superCall must be not null"; 129 return CallArgumentTranslator.translate(call, null, context()).getTranslateArguments(); 130 } 131 132 @Nullable 133 private JetDelegatorToSuperCall getSuperCall() { 134 JetDelegatorToSuperCall result = null; 135 for (JetDelegationSpecifier specifier : classDeclaration.getDelegationSpecifiers()) { 136 if (specifier instanceof JetDelegatorToSuperCall) { 137 result = (JetDelegatorToSuperCall) specifier; 138 } 139 } 140 return result; 141 } 142 143 @NotNull 144 List<JsParameter> translatePrimaryConstructorParameters() { 145 List<JetParameter> parameterList = getPrimaryConstructorParameters(classDeclaration); 146 List<JsParameter> result = new ArrayList<JsParameter>(); 147 for (JetParameter jetParameter : parameterList) { 148 result.add(translateParameter(jetParameter)); 149 } 150 return result; 151 } 152 153 @NotNull 154 private JsParameter translateParameter(@NotNull JetParameter jetParameter) { 155 DeclarationDescriptor parameterDescriptor = 156 getDescriptorForElement(bindingContext(), jetParameter); 157 JsName parameterName = context().getNameForDescriptor(parameterDescriptor); 158 JsParameter jsParameter = new JsParameter(parameterName); 159 mayBeAddInitializerStatementForProperty(jsParameter, jetParameter); 160 return jsParameter; 161 } 162 163 private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter, 164 @NotNull JetParameter jetParameter) { 165 PropertyDescriptor propertyDescriptor = 166 getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter); 167 if (propertyDescriptor == null) { 168 return; 169 } 170 JsNameRef initialValueForProperty = jsParameter.getName().makeRef(); 171 addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor); 172 } 173 174 private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) { 175 initializerStatements.add(InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue)); 176 } 177 }