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.intrinsic.functions.factories;
018    
019    import com.google.dart.compiler.backend.js.ast.JsExpression;
020    import com.google.dart.compiler.backend.js.ast.JsInvocation;
021    import com.google.dart.compiler.backend.js.ast.JsNew;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.kotlin.builtins.PrimitiveType;
025    import org.jetbrains.kotlin.descriptors.CallableDescriptor;
026    import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
027    import org.jetbrains.kotlin.descriptors.PropertyDescriptor;
028    import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
029    import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
030    import org.jetbrains.kotlin.js.patterns.DescriptorPredicate;
031    import org.jetbrains.kotlin.js.patterns.NamePredicate;
032    import org.jetbrains.kotlin.js.resolve.JsPlatform;
033    import org.jetbrains.kotlin.js.translate.callTranslator.CallInfo;
034    import org.jetbrains.kotlin.js.translate.callTranslator.CallInfoExtensionsKt;
035    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
036    import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic;
037    import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
038    import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
039    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
040    import org.jetbrains.kotlin.js.translate.utils.UtilsKt;
041    import org.jetbrains.kotlin.name.Name;
042    import org.jetbrains.kotlin.psi.KtExpression;
043    import org.jetbrains.kotlin.psi.KtQualifiedExpression;
044    import org.jetbrains.kotlin.psi.KtReferenceExpression;
045    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
046    import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
047    import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
048    import org.jetbrains.kotlin.types.KotlinType;
049    
050    import java.util.List;
051    import java.util.Map;
052    
053    import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES;
054    import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern;
055    import static org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic.CallParametersAwareFunctionIntrinsic;
056    import static org.jetbrains.kotlin.js.translate.utils.ManglingUtils.getStableMangledNameForDescriptor;
057    
058    public final class TopLevelFIF extends CompositeFIF {
059        public static final DescriptorPredicate EQUALS_IN_ANY = pattern("kotlin", "Any", "equals");
060        @NotNull
061        private static final KotlinFunctionIntrinsic KOTLIN_ANY_EQUALS = new KotlinFunctionIntrinsic("equals") {
062            @NotNull
063            @Override
064            public JsExpression apply(
065                    @NotNull CallInfo callInfo, @NotNull List<JsExpression> arguments, @NotNull TranslationContext context
066            ) {
067                if (CallInfoExtensionsKt.isSuperInvocation(callInfo)) {
068                    JsExpression dispatchReceiver = callInfo.getDispatchReceiver();
069                    assert arguments.size() == 1 && dispatchReceiver != null;
070                    return JsAstUtils.equality(dispatchReceiver, arguments.get(0));
071                }
072    
073                return super.apply(callInfo, arguments, context);
074            }
075        };
076    
077        @NotNull
078        public static final KotlinFunctionIntrinsic KOTLIN_EQUALS = new KotlinFunctionIntrinsic("equals");
079    
080        @NotNull
081        private static final DescriptorPredicate HASH_CODE_IN_ANY = pattern("kotlin", "Any", "hashCode");
082        @NotNull
083        private static final KotlinFunctionIntrinsic KOTLIN_HASH_CODE = new KotlinFunctionIntrinsic("hashCode");
084    
085        @NotNull
086        private static final FunctionIntrinsic RETURN_RECEIVER_INTRINSIC = new FunctionIntrinsic() {
087            @NotNull
088            @Override
089            public JsExpression apply(
090                    @Nullable JsExpression receiver,
091                    @NotNull List<JsExpression> arguments,
092                    @NotNull TranslationContext context
093            ) {
094                assert receiver != null;
095                return receiver;
096            }
097        };
098    
099        private static final FunctionIntrinsic NATIVE_MAP_GET = new NativeMapGetSet() {
100            @NotNull
101            @Override
102            protected String operationName() {
103                return "get";
104            }
105    
106            @Nullable
107            @Override
108            protected ExpressionReceiver getExpressionReceiver(@NotNull ResolvedCall<?> resolvedCall) {
109                ReceiverValue result = resolvedCall.getDispatchReceiver();
110                return result instanceof ExpressionReceiver ? (ExpressionReceiver) result : null;
111            }
112    
113            @Override
114            protected JsExpression asArrayAccess(
115                    @NotNull JsExpression receiver,
116                    @NotNull List<JsExpression> arguments,
117                    @NotNull TranslationContext context
118            ) {
119                return ArrayFIF.GET_INTRINSIC.apply(receiver, arguments, context);
120            }
121        };
122    
123        private static final FunctionIntrinsic NATIVE_MAP_SET = new NativeMapGetSet() {
124            @NotNull
125            @Override
126            protected String operationName() {
127                return "put";
128            }
129    
130            @Nullable
131            @Override
132            protected ExpressionReceiver getExpressionReceiver(@NotNull ResolvedCall<?> resolvedCall) {
133                ReceiverValue result = resolvedCall.getExtensionReceiver();
134                return result instanceof ExpressionReceiver ? (ExpressionReceiver) result : null;
135            }
136    
137            @Override
138            protected JsExpression asArrayAccess(
139                    @NotNull JsExpression receiver,
140                    @NotNull List<JsExpression> arguments,
141                    @NotNull TranslationContext context
142            ) {
143                return ArrayFIF.SET_INTRINSIC.apply(receiver, arguments, context);
144            }
145        };
146    
147        private static final FunctionIntrinsic JS_CLASS_FUN_INTRINSIC = new FunctionIntrinsic() {
148            @NotNull
149            @Override
150            public JsExpression apply(
151                    @NotNull CallInfo callInfo, @NotNull List<JsExpression> arguments, @NotNull TranslationContext context
152            ) {
153                ResolvedCall<? extends CallableDescriptor> resolvedCall = callInfo.getResolvedCall();
154                Map<TypeParameterDescriptor, KotlinType> typeArguments = resolvedCall.getTypeArguments();
155    
156                assert typeArguments.size() == 1;
157                KotlinType type = typeArguments.values().iterator().next();
158    
159                return UtilsKt.getReferenceToJsClass(type, context);
160            }
161    
162            @NotNull
163            @Override
164            public JsExpression apply(
165                    @Nullable JsExpression receiver, @NotNull List<JsExpression> arguments, @NotNull TranslationContext context
166            ) {
167                throw new IllegalStateException();
168            }
169        };
170    
171        @NotNull
172        public static final KotlinFunctionIntrinsic TO_STRING = new KotlinFunctionIntrinsic("toString");
173    
174        @NotNull
175        public static final FunctionIntrinsicFactory INSTANCE = new TopLevelFIF();
176    
177        private TopLevelFIF() {
178            add(EQUALS_IN_ANY, KOTLIN_ANY_EQUALS);
179            add(pattern("kotlin", "toString").isExtensionOf(FQ_NAMES.any.asString()), TO_STRING);
180            add(pattern("kotlin", "equals").isExtensionOf(FQ_NAMES.any.asString()), KOTLIN_EQUALS);
181            add(HASH_CODE_IN_ANY, KOTLIN_HASH_CODE);
182            add(pattern(NamePredicate.PRIMITIVE_NUMBERS, "equals"), KOTLIN_EQUALS);
183            add(pattern("String|Boolean|Char|Number.equals"), KOTLIN_EQUALS);
184            add(pattern("kotlin", "arrayOfNulls"), new KotlinFunctionIntrinsic("nullArray"));
185            add(pattern("kotlin", "iterator").isExtensionOf(FQ_NAMES.iterator.asString()), RETURN_RECEIVER_INTRINSIC);
186    
187            add(pattern("kotlin.collections", "Map", "get").checkOverridden(), NATIVE_MAP_GET);
188            add(pattern("kotlin.js", "set").isExtensionOf(FQ_NAMES.mutableMap.asString()), NATIVE_MAP_SET);
189    
190            add(pattern("java.util", "HashMap", "<init>"), new MapSelectImplementationIntrinsic(false));
191            add(pattern("java.util", "HashSet", "<init>"), new MapSelectImplementationIntrinsic(true));
192    
193            add(pattern("kotlin.js", "Json", "get"), ArrayFIF.GET_INTRINSIC);
194            add(pattern("kotlin.js", "Json", "set"), ArrayFIF.SET_INTRINSIC);
195    
196            add(pattern("kotlin", "Throwable", "getMessage"), MESSAGE_PROPERTY_INTRINSIC);
197    
198            add(pattern("kotlin.js", "jsClass"), JS_CLASS_FUN_INTRINSIC);
199        }
200    
201        private abstract static class NativeMapGetSet extends CallParametersAwareFunctionIntrinsic {
202            @NotNull
203            protected abstract String operationName();
204    
205            @Nullable
206            protected abstract ExpressionReceiver getExpressionReceiver(@NotNull ResolvedCall<?> resolvedCall);
207    
208            protected abstract JsExpression asArrayAccess(
209                    @NotNull JsExpression receiver,
210                    @NotNull List<JsExpression> arguments,
211                    @NotNull TranslationContext context
212            );
213    
214            @NotNull
215            @Override
216            public JsExpression apply(@NotNull CallInfo callInfo, @NotNull List<JsExpression> arguments, @NotNull TranslationContext context) {
217                ExpressionReceiver expressionReceiver = getExpressionReceiver(callInfo.getResolvedCall());
218                JsExpression thisOrReceiver = getThisOrReceiverOrNull(callInfo);
219                assert thisOrReceiver != null;
220                if (expressionReceiver != null) {
221                    KtExpression expression = expressionReceiver.getExpression();
222                    KtReferenceExpression referenceExpression = null;
223                    if (expression instanceof KtReferenceExpression) {
224                        referenceExpression = (KtReferenceExpression) expression;
225                    }
226                    else if (expression instanceof KtQualifiedExpression) {
227                        KtExpression candidate = ((KtQualifiedExpression) expression).getReceiverExpression();
228                        if (candidate instanceof KtReferenceExpression) {
229                            referenceExpression = (KtReferenceExpression) candidate;
230                        }
231                    }
232    
233                    if (referenceExpression != null) {
234                        DeclarationDescriptor candidate = BindingUtils.getDescriptorForReferenceExpression(context.bindingContext(),
235                                                                                                           referenceExpression);
236                        if (candidate instanceof PropertyDescriptor && AnnotationsUtils.isNativeObject(candidate)) {
237                            return asArrayAccess(thisOrReceiver, arguments, context);
238                        }
239                    }
240                }
241    
242                String mangledName = getStableMangledNameForDescriptor(JsPlatform.INSTANCE.getBuiltIns().getMutableMap(), operationName());
243    
244                return new JsInvocation(JsAstUtils.pureFqn(mangledName, thisOrReceiver), arguments);
245            }
246        }
247    
248        private static class MapSelectImplementationIntrinsic extends CallParametersAwareFunctionIntrinsic {
249            private final boolean isSet;
250    
251            private MapSelectImplementationIntrinsic(boolean isSet) {
252                this.isSet = isSet;
253            }
254    
255            @NotNull
256            @Override
257            public JsExpression apply(
258                    @NotNull CallInfo callInfo,
259                    @NotNull List<JsExpression> arguments,
260                    @NotNull TranslationContext context
261            ) {
262                KotlinType keyType = callInfo.getResolvedCall().getTypeArguments().values().iterator().next();
263                Name keyTypeName = DescriptorUtilsKt.getNameIfStandardType(keyType);
264                String collectionClassName = null;
265                if (keyTypeName != null) {
266                    if (NamePredicate.PRIMITIVE_NUMBERS.apply(keyTypeName)) {
267                        collectionClassName = isSet ? "PrimitiveNumberHashSet" : "PrimitiveNumberHashMap";
268                    }
269                    else if (PrimitiveType.BOOLEAN.getTypeName().equals(keyTypeName)) {
270                        collectionClassName = isSet ? "PrimitiveBooleanHashSet" : "PrimitiveBooleanHashMap";
271                    }
272                    else if (keyTypeName.asString().equals("String")) {
273                        collectionClassName = isSet ? "DefaultPrimitiveHashSet" : "DefaultPrimitiveHashMap";
274                    }
275                }
276    
277                if (collectionClassName == null ) {
278                    collectionClassName = isSet ? "ComplexHashSet" : "ComplexHashMap";
279                }
280    
281                return new JsNew(context.namer().kotlin(collectionClassName), arguments);
282            }
283        }
284    }