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.intellij.openapi.util.text.StringUtil;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.descriptors.CallableDescriptor;
023    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
024    import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor;
025    import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
026    import org.jetbrains.kotlin.idea.KotlinLanguage;
027    import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
028    import org.jetbrains.kotlin.js.backend.ast.*;
029    import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties;
030    import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind;
031    import org.jetbrains.kotlin.js.backend.ast.metadata.TypeCheck;
032    import org.jetbrains.kotlin.js.naming.NameSuggestion;
033    import org.jetbrains.kotlin.js.naming.SuggestedName;
034    import org.jetbrains.kotlin.js.resolve.JsPlatform;
035    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
036    import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils;
037    import org.jetbrains.kotlin.name.FqName;
038    import org.jetbrains.kotlin.name.FqNameUnsafe;
039    import org.jetbrains.kotlin.name.Name;
040    import org.jetbrains.kotlin.resolve.DescriptorUtils;
041    
042    import java.util.Arrays;
043    import java.util.Collection;
044    import java.util.Collections;
045    import java.util.List;
046    
047    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn;
048    import static org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName;
049    
050    /**
051     * Encapsulates different types of constants and naming conventions.
052     */
053    public final class Namer {
054        public static final String KOTLIN_NAME = KotlinLanguage.NAME;
055        public static final String KOTLIN_LOWER_NAME = KOTLIN_NAME.toLowerCase();
056    
057        public static final String EQUALS_METHOD_NAME = getStableMangledNameForDescriptor(JsPlatform.INSTANCE.getBuiltIns().getAny(), "equals");
058        public static final String COMPARE_TO_METHOD_NAME = getStableMangledNameForDescriptor(JsPlatform.INSTANCE.getBuiltIns().getComparable(), "compareTo");
059        public static final String LONG_FROM_NUMBER = "fromNumber";
060        public static final String LONG_TO_NUMBER = "toNumber";
061        public static final String LONG_FROM_INT = "fromInt";
062        public static final String LONG_ZERO = "ZERO";
063        public static final String LONG_ONE = "ONE";
064        public static final String LONG_NEG_ONE = "NEG_ONE";
065        public static final String PRIMITIVE_COMPARE_TO = "primitiveCompareTo";
066        public static final String IS_CHAR = "isChar";
067        public static final String IS_NUMBER = "isNumber";
068        private static final String IS_CHAR_SEQUENCE = "isCharSequence";
069        public static final String GET_KCLASS = "getKClass";
070        public static final String GET_KCLASS_FROM_EXPRESSION = "getKClassFromExpression";
071    
072        public static final String CALLEE_NAME = "$fun";
073    
074        public static final String CALL_FUNCTION = "call";
075        private static final String APPLY_FUNCTION = "apply";
076    
077        public static final String OUTER_FIELD_NAME = "$outer";
078    
079        private static final String DELEGATE = "$delegate";
080    
081        private static final String ROOT_PACKAGE = "_";
082    
083        private static final String RECEIVER_PARAMETER_NAME = "$receiver";
084        public static final String ANOTHER_THIS_PARAMETER_NAME = "$this";
085    
086        private static final String THROW_NPE_FUN_NAME = "throwNPE";
087        private static final String THROW_CLASS_CAST_EXCEPTION_FUN_NAME = "throwCCE";
088        private static final String THROW_ILLEGAL_STATE_EXCEPTION_FUN_NAME = "throwISE";
089        private static final String PROTOTYPE_NAME = "prototype";
090        private static final String CAPTURED_VAR_FIELD = "v";
091    
092        public static final JsNameRef IS_ARRAY_FUN_REF = new JsNameRef("isArray", "Array");
093        public static final String DEFINE_INLINE_FUNCTION = "defineInlineFunction";
094        public static final String DEFAULT_PARAMETER_IMPLEMENTOR_SUFFIX = "$default";
095    
096        private static final JsNameRef JS_OBJECT = new JsNameRef("Object");
097        private static final JsNameRef JS_OBJECT_CREATE_FUNCTION = new JsNameRef("create", JS_OBJECT);
098    
099        public static final String LOCAL_MODULE_PREFIX = "$module$";
100        public static final String METADATA = "$metadata$";
101        public static final String METADATA_SUPERTYPES = "interfaces";
102        public static final String METADATA_SIMPLE_NAME = "simpleName";
103        public static final String METADATA_CLASS_KIND = "kind";
104        public static final String CLASS_KIND_ENUM = "Kind";
105        public static final String CLASS_KIND_CLASS = "CLASS";
106        public static final String CLASS_KIND_INTERFACE = "INTERFACE";
107        public static final String CLASS_KIND_OBJECT = "OBJECT";
108    
109        public static final String OBJECT_INSTANCE_VAR_SUFFIX = "_instance";
110        public static final String OBJECT_INSTANCE_FUNCTION_SUFFIX = "_getInstance";
111    
112        public static final String ENUM_NAME_FIELD = "name$";
113        public static final String ENUM_ORDINAL_FIELD = "ordinal$";
114    
115        @NotNull
116        public static String getFunctionTag(@NotNull CallableDescriptor functionDescriptor) {
117            functionDescriptor = (CallableDescriptor) JsDescriptorUtils.findRealInlineDeclaration(functionDescriptor);
118            String moduleName = getModuleName(functionDescriptor);
119            FqNameUnsafe fqNameParent = DescriptorUtils.getFqName(functionDescriptor).parent();
120            String qualifier = null;
121    
122            if (!fqNameParent.isRoot()) {
123                qualifier = fqNameParent.asString();
124            }
125    
126            SuggestedName suggestedName = new NameSuggestion().suggest(functionDescriptor);
127            assert suggestedName != null : "Suggested name can be null only for module descriptors: " + functionDescriptor;
128            String mangledName = suggestedName.getNames().get(0);
129            return StringUtil.join(Arrays.asList(moduleName, qualifier, mangledName), ".");
130        }
131    
132        @NotNull
133        public static String getReceiverParameterName() {
134            return RECEIVER_PARAMETER_NAME;
135        }
136    
137        @NotNull
138        public static String getRootPackageName() {
139            return ROOT_PACKAGE;
140        }
141    
142        @NotNull
143        public static String getPrototypeName() {
144            return PROTOTYPE_NAME;
145        }
146    
147        @NotNull
148        public static String getDelegatePrefix() {
149            return DELEGATE;
150        }
151    
152        @NotNull
153        public static String getDelegateName(@NotNull String propertyName) {
154            return propertyName + DELEGATE;
155        }
156    
157        @NotNull
158        public static JsNameRef getDelegateNameRef(String propertyName) {
159            return new JsNameRef(getDelegateName(propertyName), JsLiteral.THIS);
160        }
161    
162        @NotNull
163        public static JsNameRef getFunctionCallRef(@NotNull JsExpression functionExpression) {
164            return pureFqn(CALL_FUNCTION, functionExpression);
165        }
166    
167        @NotNull
168        public static JsNameRef getFunctionApplyRef(@NotNull JsExpression functionExpression) {
169            return pureFqn(APPLY_FUNCTION, functionExpression);
170        }
171    
172        @NotNull
173        public static JsInvocation createObjectWithPrototypeFrom(@NotNull JsExpression referenceToClass) {
174            return new JsInvocation(JS_OBJECT_CREATE_FUNCTION.deepCopy(), JsAstUtils.prototypeOf(referenceToClass));
175        }
176    
177        @NotNull
178        public static JsNameRef getCapturedVarAccessor(@NotNull JsExpression ref) {
179            return pureFqn(CAPTURED_VAR_FIELD, ref);
180        }
181    
182        @NotNull
183        public static String isInstanceSuggestedName(@NotNull TypeParameterDescriptor descriptor) {
184            return "is" + descriptor.getName().getIdentifier();
185        }
186    
187        @NotNull
188        public static Namer newInstance(@NotNull JsScope rootScope) {
189            return new Namer(rootScope);
190        }
191    
192        @NotNull
193        private final JsObjectScope kotlinScope;
194        @NotNull
195        public static final String FUNCTION_CALLABLE_REF = "getCallableRef";
196        @NotNull
197        public static final String PROPERTY_CALLABLE_REF = "getPropertyCallableRef";
198        @NotNull
199        private final JsExpression callGetProperty;
200        @NotNull
201        private final JsExpression callSetProperty;
202    
203        @NotNull
204        private final JsName isTypeName;
205    
206        private Namer(@NotNull JsScope rootScope) {
207            kotlinScope = new JsObjectScope(rootScope, "Kotlin standard object");
208    
209            callGetProperty = kotlin("callGetter");
210            callSetProperty = kotlin("callSetter");
211    
212            isTypeName = kotlinScope.declareName("isType");
213        }
214    
215        // TODO: get rid of this function
216        @NotNull
217        public static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) {
218            Collection<SimpleFunctionDescriptor> functions = descriptor.getDefaultType().getMemberScope().getContributedFunctions(
219                    Name.identifier(functionName), NoLookupLocation.FROM_BACKEND);
220            assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor;
221            SuggestedName suggested = new NameSuggestion().suggest(functions.iterator().next());
222            assert suggested != null : "Suggested name for class members is always non-null: " + functions.iterator().next();
223            return suggested.getNames().get(0);
224        }
225    
226        @NotNull
227        public static JsExpression throwNPEFunctionRef() {
228            return new JsNameRef(THROW_NPE_FUN_NAME, kotlinObject());
229        }
230    
231        @NotNull
232        public static JsExpression throwClassCastExceptionFunRef() {
233            return new JsNameRef(THROW_CLASS_CAST_EXCEPTION_FUN_NAME, kotlinObject());
234        }
235    
236        @NotNull
237        public static JsExpression throwIllegalStateExceptionFunRef() {
238            return new JsNameRef(THROW_ILLEGAL_STATE_EXCEPTION_FUN_NAME, kotlinObject());
239        }
240    
241        @NotNull
242        public static JsNameRef kotlin(@NotNull JsName name) {
243            return pureFqn(name, kotlinObject());
244        }
245    
246        @NotNull
247        public JsNameRef kotlin(@NotNull String name) {
248            return kotlin(kotlinScope.declareName(name));
249        }
250    
251        @NotNull
252        public static JsNameRef kotlinObject() {
253            return pureFqn(KOTLIN_NAME, null);
254        }
255    
256        @NotNull
257        public JsExpression isTypeOf(@NotNull JsExpression type) {
258            return invokeFunctionAndSetTypeCheckMetadata("isTypeOf", type, TypeCheck.TYPEOF);
259        }
260    
261        @NotNull
262        public JsExpression isInstanceOf(@NotNull JsExpression type) {
263            return invokeFunctionAndSetTypeCheckMetadata("isInstanceOf", type, TypeCheck.INSTANCEOF);
264        }
265    
266        @NotNull
267        public JsExpression orNull(@NotNull JsExpression callable) {
268            return invokeFunctionAndSetTypeCheckMetadata("orNull", callable, TypeCheck.OR_NULL);
269        }
270    
271        @NotNull
272        public JsExpression andPredicate(@NotNull JsExpression a, @NotNull JsExpression b) {
273            return invokeFunctionAndSetTypeCheckMetadata("andPredicate", Arrays.asList(a, b), TypeCheck.AND_PREDICATE);
274        }
275    
276        @NotNull
277        public JsExpression isComparable() {
278            return kotlin("isComparable");
279        }
280    
281        @NotNull
282        public JsExpression isCharSequence() {
283            return kotlin(IS_CHAR_SEQUENCE);
284        }
285    
286        @NotNull
287        private JsExpression invokeFunctionAndSetTypeCheckMetadata(
288                @NotNull String functionName,
289                @Nullable JsExpression argument,
290                @NotNull TypeCheck metadata
291        ) {
292            List<JsExpression> arguments = argument != null ? Collections.singletonList(argument) : Collections.<JsExpression>emptyList();
293            return invokeFunctionAndSetTypeCheckMetadata(functionName, arguments, metadata);
294        }
295    
296        @NotNull
297        private JsExpression invokeFunctionAndSetTypeCheckMetadata(
298                @NotNull String functionName,
299                @NotNull List<JsExpression> arguments,
300                @NotNull TypeCheck metadata
301        ) {
302            JsInvocation invocation = new JsInvocation(kotlin(functionName));
303            invocation.getArguments().addAll(arguments);
304            MetadataProperties.setTypeCheck(invocation, metadata);
305            MetadataProperties.setSideEffects(invocation, SideEffectKind.PURE);
306            return invocation;
307        }
308    
309        @NotNull
310        public JsExpression isInstanceOf(@NotNull JsExpression instance, @NotNull JsExpression type) {
311            JsInvocation result = new JsInvocation(kotlin(isTypeName), instance, type);
312            MetadataProperties.setSideEffects(result, SideEffectKind.PURE);
313            return result;
314        }
315    
316        @NotNull
317        static String generatePackageName(@NotNull FqName packageFqName) {
318            return packageFqName.isRoot() ? getRootPackageName() : packageFqName.shortName().asString();
319        }
320    
321        @NotNull
322        public static JsExpression getUndefinedExpression() {
323            return new JsPrefixOperation(JsUnaryOperator.VOID, JsNumberLiteral.ZERO);
324        }
325    
326        @NotNull
327        public JsExpression getCallGetProperty() {
328            return callGetProperty.deepCopy();
329        }
330    
331        @NotNull
332        public JsExpression getCallSetProperty() {
333            return callSetProperty.deepCopy();
334        }
335    
336        public static JsNameRef kotlinLong() {
337            return pureFqn("Long", kotlinObject());
338        }
339    
340        @NotNull
341        public static JsNameRef createInlineFunction() {
342            return pureFqn(DEFINE_INLINE_FUNCTION, kotlinObject());
343        }
344    
345        @NotNull
346        public static String suggestedModuleName(@NotNull String id) {
347            if (id.isEmpty()) {
348                return "_";
349            }
350    
351            StringBuilder sb = new StringBuilder(id.length());
352            char c = id.charAt(0);
353            if (Character.isJavaIdentifierStart(c)) {
354                sb.append(c);
355            }
356            else {
357                sb.append('_');
358                if (Character.isJavaIdentifierPart(c)) {
359                    sb.append(c);
360                }
361            }
362    
363            for (int i = 1; i < id.length(); ++i) {
364                c = id.charAt(i);
365                sb.append(Character.isJavaIdentifierPart(c) ? c : '_');
366            }
367    
368            return sb.toString();
369        }
370    
371        public static JsNameRef imul() {
372            return pureFqn("imul", kotlinObject());
373        }
374    
375        public static boolean requiresEscaping(@NotNull String name) {
376            // TODO: remove if there is existing implementation of this method
377            // TODO: handle JavaScript keywords
378            if (name.isEmpty() || !Character.isJavaIdentifierStart(name.charAt(0))) return true;
379            for (int i = 1; i < name.length(); ++i) {
380                if (!Character.isJavaIdentifierPart(name.charAt(i))) return true;
381            }
382            return false;
383        }
384    }