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