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