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