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.declaration;
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.jet.lang.descriptors.ClassDescriptor;
023 import org.jetbrains.jet.lang.descriptors.ClassKind;
024 import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
025 import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor;
026 import org.jetbrains.jet.lang.psi.JetClassOrObject;
027 import org.jetbrains.jet.lang.psi.JetObjectDeclaration;
028 import org.jetbrains.jet.lang.psi.JetParameter;
029 import org.jetbrains.jet.lang.types.JetType;
030 import org.jetbrains.jet.lang.types.TypeConstructor;
031 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
032 import org.jetbrains.k2js.translate.context.DefinitionPlace;
033 import org.jetbrains.k2js.translate.context.Namer;
034 import org.jetbrains.k2js.translate.context.TranslationContext;
035 import org.jetbrains.k2js.translate.expression.ExpressionPackage;
036 import org.jetbrains.k2js.translate.general.AbstractTranslator;
037 import org.jetbrains.k2js.translate.initializer.ClassInitializerTranslator;
038 import org.jetbrains.k2js.translate.utils.JsAstUtils;
039
040 import java.util.*;
041
042 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.*;
043 import static org.jetbrains.jet.lang.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances;
044 import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createClassObjectInitializer;
045 import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
046 import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
047 import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
048 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass;
049 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
050 import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
051 import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction;
052
053 /**
054 * Generates a definition of a single class.
055 */
056 public final class ClassTranslator extends AbstractTranslator {
057 @NotNull
058 private final JetClassOrObject classDeclaration;
059
060 @NotNull
061 private final ClassDescriptor descriptor;
062
063 @NotNull
064 public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
065 return new ClassTranslator(classDeclaration, context).translate();
066 }
067
068 @NotNull
069 public static JsExpression generateObjectLiteral(@NotNull JetObjectDeclaration objectDeclaration, @NotNull TranslationContext context) {
070 return new ClassTranslator(objectDeclaration, context).translateObjectLiteralExpression();
071 }
072
073 private ClassTranslator(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
074 super(context);
075 this.classDeclaration = classDeclaration;
076 this.descriptor = getClassDescriptor(context.bindingContext(), classDeclaration);
077 }
078
079 @NotNull
080 private JsExpression translateObjectLiteralExpression() {
081 ClassDescriptor containingClass = getContainingClass(descriptor);
082 if (containingClass == null) {
083 return translate(context());
084 }
085
086 return translateObjectInsideClass(context());
087 }
088
089 @NotNull
090 public JsInvocation translate() {
091 return translate(context());
092 }
093
094 @NotNull
095 public JsInvocation translate(@NotNull TranslationContext declarationContext) {
096 return new JsInvocation(context().namer().classCreateInvocation(descriptor), getClassCreateInvocationArguments(declarationContext));
097 }
098
099 private boolean isTrait() {
100 return descriptor.getKind().equals(ClassKind.TRAIT);
101 }
102
103 private List<JsExpression> getClassCreateInvocationArguments(@NotNull TranslationContext declarationContext) {
104 List<JsExpression> invocationArguments = new ArrayList<JsExpression>();
105
106 List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
107 List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
108
109 boolean isTopLevelDeclaration = context() == declarationContext;
110
111 JsNameRef qualifiedReference = null;
112 if (isTopLevelDeclaration) {
113 DefinitionPlace definitionPlace = null;
114
115 if (!descriptor.getKind().isSingleton() && !isAnonymousObject(descriptor)) {
116 qualifiedReference = declarationContext.getQualifiedReference(descriptor);
117 JsScope scope = context().getScopeForDescriptor(descriptor);
118 definitionPlace = new DefinitionPlace(scope, qualifiedReference, staticProperties);
119 }
120
121 declarationContext = declarationContext.newDeclaration(descriptor, definitionPlace);
122 }
123
124 declarationContext = fixContextForClassObjectAccessing(declarationContext);
125
126 invocationArguments.add(getSuperclassReferences(declarationContext));
127 if (!isTrait()) {
128 JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod();
129 invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer);
130 }
131
132 translatePropertiesAsConstructorParameters(declarationContext, properties);
133 DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties);
134 bodyVisitor.traverseContainer(classDeclaration, declarationContext);
135 mayBeAddEnumEntry(bodyVisitor.getEnumEntryList(), staticProperties, declarationContext);
136
137 if (KotlinBuiltIns.getInstance().isData(descriptor)) {
138 new JsDataClassGenerator(classDeclaration, declarationContext, properties).generate();
139 }
140
141 boolean hasStaticProperties = !staticProperties.isEmpty();
142 if (!properties.isEmpty() || hasStaticProperties) {
143 if (properties.isEmpty()) {
144 invocationArguments.add(JsLiteral.NULL);
145 }
146 else {
147 if (qualifiedReference != null) {
148 // about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
149 invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, new JsNameRef("prototype", qualifiedReference)));
150 }
151 invocationArguments.add(new JsObjectLiteral(properties, true));
152 }
153 }
154 if (hasStaticProperties) {
155 invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference));
156 invocationArguments.add(new JsObjectLiteral(staticProperties, true));
157 }
158 return invocationArguments;
159 }
160
161 private TranslationContext fixContextForClassObjectAccessing(TranslationContext declarationContext) {
162 // In Kotlin we can access to class object members without qualifier just by name, but we should translate it to access with FQ name.
163 // So create alias for class object receiver parameter.
164 ClassDescriptor classObjectDescriptor = descriptor.getClassObjectDescriptor();
165 if (classObjectDescriptor != null) {
166 JsExpression referenceToClass = translateAsFQReference(classObjectDescriptor.getContainingDeclaration(), declarationContext);
167 JsExpression classObjectAccessor = Namer.getClassObjectAccessor(referenceToClass);
168 ReceiverParameterDescriptor classObjectReceiver = getReceiverParameterForDeclaration(classObjectDescriptor);
169 declarationContext.aliasingContext().registerAlias(classObjectReceiver, classObjectAccessor);
170 }
171
172 // Overlap alias of class object receiver for accessing from containing class(see previous if block),
173 // because inside class object we should use simple name for access.
174 if (descriptor.getKind() == ClassKind.CLASS_OBJECT) {
175 declarationContext = declarationContext.innerContextWithAliased(descriptor.getThisAsReceiverParameter(), JsLiteral.THIS);
176 }
177
178 return declarationContext;
179 }
180
181 private void mayBeAddEnumEntry(@NotNull List<JsPropertyInitializer> enumEntryList,
182 @NotNull List<JsPropertyInitializer> staticProperties,
183 @NotNull TranslationContext declarationContext
184 ) {
185 if (descriptor.getKind() == ClassKind.ENUM_CLASS) {
186 JsInvocation invocation = context().namer().enumEntriesObjectCreateInvocation();
187 invocation.getArguments().add(new JsObjectLiteral(enumEntryList, true));
188
189 JsFunction fun = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), invocation);
190 staticProperties.add(createClassObjectInitializer(fun, declarationContext));
191 } else {
192 assert enumEntryList.isEmpty(): "Only enum class may have enum entry. Class kind is: " + descriptor.getKind();
193 }
194 }
195
196 private JsExpression getSuperclassReferences(@NotNull TranslationContext declarationContext) {
197 List<JsExpression> superClassReferences = getSupertypesNameReferences();
198 if (superClassReferences.isEmpty()) {
199 return JsLiteral.NULL;
200 } else {
201 return simpleReturnFunction(declarationContext.scope(), new JsArrayLiteral(superClassReferences));
202 }
203 }
204
205 @NotNull
206 private List<JsExpression> getSupertypesNameReferences() {
207 List<JetType> supertypes = getSupertypesWithoutFakes(descriptor);
208 if (supertypes.isEmpty()) {
209 return Collections.emptyList();
210 }
211 if (supertypes.size() == 1) {
212 JetType type = supertypes.get(0);
213 ClassDescriptor supertypeDescriptor = getClassDescriptorForType(type);
214 return Collections.<JsExpression>singletonList(getClassReference(supertypeDescriptor));
215 }
216
217 Set<TypeConstructor> supertypeConstructors = new HashSet<TypeConstructor>();
218 for (JetType type : supertypes) {
219 supertypeConstructors.add(type.getConstructor());
220 }
221 List<TypeConstructor> sortedAllSuperTypes = topologicallySortSuperclassesAndRecordAllInstances(descriptor.getDefaultType(),
222 new HashMap<TypeConstructor, Set<JetType>>(),
223 new HashSet<TypeConstructor>());
224 List<JsExpression> supertypesRefs = new ArrayList<JsExpression>();
225 for (TypeConstructor typeConstructor : sortedAllSuperTypes) {
226 if (supertypeConstructors.contains(typeConstructor)) {
227 ClassDescriptor supertypeDescriptor = getClassDescriptorForTypeConstructor(typeConstructor);
228 supertypesRefs.add(getClassReference(supertypeDescriptor));
229 }
230 }
231 return supertypesRefs;
232 }
233
234 @NotNull
235 private JsNameRef getClassReference(@NotNull ClassDescriptor superClassDescriptor) {
236 return context().getQualifiedReference(superClassDescriptor);
237 }
238
239 private void translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext,
240 @NotNull List<JsPropertyInitializer> result) {
241 for (JetParameter parameter : getPrimaryConstructorParameters(classDeclaration)) {
242 PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
243 if (descriptor != null) {
244 PropertyTranslator.translateAccessors(descriptor, result, classDeclarationContext);
245 }
246 }
247 }
248
249 @NotNull
250 private JsExpression translateObjectInsideClass(@NotNull TranslationContext outerClassContext) {
251 JsFunction fun = new JsFunction(outerClassContext.scope(), new JsBlock());
252 TranslationContext funContext = outerClassContext.newFunctionBodyWithUsageTracker(fun, descriptor);
253
254 fun.getBody().getStatements().add(new JsReturn(translate(funContext)));
255
256 return ExpressionPackage.withCapturedParameters(fun, funContext, outerClassContext, descriptor);
257 }
258 }