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