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 KotlinFunctionIntrinsic TO_STRING = new KotlinFunctionIntrinsic("toString");
128    
129        @NotNull
130        public static final FunctionIntrinsicFactory INSTANCE = new TopLevelFIF();
131    
132        private TopLevelFIF() {
133            add(pattern("jet", "toString").receiverExists(), TO_STRING);
134            add(pattern("jet", "equals").receiverExists(), EQUALS);
135            add(pattern("jet", "identityEquals").receiverExists(), IDENTITY_EQUALS);
136            add(pattern(NamePredicate.PRIMITIVE_NUMBERS, "equals"), EQUALS);
137            add(pattern("String|Boolean|Char|Number.equals"), EQUALS);
138            add(pattern("jet", "arrayOfNulls"), new KotlinFunctionIntrinsic("nullArray"));
139            add(pattern("jet", "iterator").receiverExists(), RETURN_RECEIVER_INTRINSIC);
140            add(new DescriptorPredicate() {
141                    @Override
142                    public boolean apply(@NotNull FunctionDescriptor descriptor) {
143                        if (!descriptor.getName().asString().equals("invoke")) {
144                            return false;
145                        }
146                        int parameterCount = descriptor.getValueParameters().size();
147                        DeclarationDescriptor fun = descriptor.getContainingDeclaration();
148                        return fun == (descriptor.getReceiverParameter() == null
149                                       ? KotlinBuiltIns.getInstance().getFunction(parameterCount)
150                                       : KotlinBuiltIns.getInstance().getExtensionFunction(parameterCount));
151                    }
152                }, new CallParametersAwareFunctionIntrinsic() {
153                    @NotNull
154                    @Override
155                    public JsExpression apply(
156                            @NotNull CallTranslator callTranslator,
157                            @NotNull List<JsExpression> arguments,
158                            @NotNull TranslationContext context
159                    ) {
160                        JsExpression thisExpression = callTranslator.getCallParameters().getThisObject();
161                        JsExpression functionReference = callTranslator.getCallParameters().getFunctionReference();
162                        if (thisExpression == null) {
163                            return new JsInvocation(functionReference, arguments);
164                        }
165                        else if (callTranslator.getResolvedCall().getReceiverArgument().exists()) {
166                            return callTranslator.extensionFunctionCall(false);
167                        }
168                        else {
169                            if (functionReference instanceof JsNameRef && ((JsNameRef) functionReference).getIdent().equals("invoke")) {
170                                return callTranslator.explicitInvokeCall();
171                            }
172                            return new JsInvocation(new JsNameRef("call", functionReference), generateInvocationArguments(thisExpression, arguments));
173                        }
174                    }
175                }
176            );
177    
178            add(pattern("jet", "Map", "get").checkOverridden(), NATIVE_MAP_GET);
179            add(pattern("js", "set").receiverExists(), NATIVE_MAP_SET);
180    
181            String[] javaUtil = {"java", "util"};
182            add(pattern(javaUtil, "HashMap", "<init>"), new MapSelectImplementationIntrinsic(false));
183            add(pattern(javaUtil, "HashSet", "<init>"), new MapSelectImplementationIntrinsic(true));
184    
185            add(pattern("js", "Json", "get"), ArrayFIF.GET_INTRINSIC);
186            add(pattern("js", "Json", "set"), ArrayFIF.SET_INTRINSIC);
187        }
188    
189        private abstract static class NativeMapGetSet extends CallParametersAwareFunctionIntrinsic {
190            @NotNull
191            protected abstract String operation();
192    
193            @Nullable
194            protected abstract ExpressionReceiver getExpressionReceiver(@NotNull ResolvedCall<?> resolvedCall);
195    
196            protected abstract JsExpression asArrayAccess(
197                    @NotNull JsExpression receiver,
198                    @NotNull List<JsExpression> arguments,
199                    @NotNull TranslationContext context
200            );
201    
202            @NotNull
203            @Override
204            public JsExpression apply(@NotNull CallTranslator callTranslator, @NotNull List<JsExpression> arguments, @NotNull TranslationContext context) {
205                ExpressionReceiver expressionReceiver = getExpressionReceiver(callTranslator.getResolvedCall());
206                JsExpression thisOrReceiver = callTranslator.getCallParameters().getThisOrReceiverOrNull();
207                assert thisOrReceiver != null;
208                if (expressionReceiver != null) {
209                    JetExpression expression = expressionReceiver.getExpression();
210                    JetReferenceExpression referenceExpression = null;
211                    if (expression instanceof JetReferenceExpression) {
212                        referenceExpression = (JetReferenceExpression) expression;
213                    }
214                    else if (expression instanceof JetQualifiedExpression) {
215                        JetExpression candidate = ((JetQualifiedExpression) expression).getReceiverExpression();
216                        if (candidate instanceof JetReferenceExpression) {
217                            referenceExpression = (JetReferenceExpression) candidate;
218                        }
219                    }
220    
221                    if (referenceExpression != null) {
222                        DeclarationDescriptor candidate = BindingUtils.getDescriptorForReferenceExpression(context.bindingContext(),
223                                                                                                           referenceExpression);
224                        if (candidate instanceof PropertyDescriptor && AnnotationsUtils.isNativeObject(candidate)) {
225                            return asArrayAccess(thisOrReceiver, arguments, context);
226                        }
227                    }
228                }
229    
230                return new JsInvocation(new JsNameRef(operation(), thisOrReceiver), arguments);
231            }
232        }
233    
234        private static class MapSelectImplementationIntrinsic extends CallParametersAwareFunctionIntrinsic {
235            private final boolean isSet;
236    
237            private MapSelectImplementationIntrinsic(boolean isSet) {
238                this.isSet = isSet;
239            }
240    
241            @NotNull
242            @Override
243            public JsExpression apply(
244                    @NotNull CallTranslator callTranslator,
245                    @NotNull List<JsExpression> arguments,
246                    @NotNull TranslationContext context
247            ) {
248                JetType keyType = callTranslator.getResolvedCall().getTypeArguments().values().iterator().next();
249                Name keyTypeName = JsDescriptorUtils.getNameIfStandardType(keyType);
250                String collectionClassName;
251                if (keyTypeName != null &&
252                    (NamePredicate.PRIMITIVE_NUMBERS.apply(keyTypeName) ||
253                     keyTypeName.asString().equals("String") ||
254                     PrimitiveType.BOOLEAN.getTypeName().equals(keyTypeName))) {
255                    collectionClassName = isSet ? "PrimitiveHashSet" : "PrimitiveHashMap";
256                }
257                else {
258                    collectionClassName = isSet ? "ComplexHashSet" : "ComplexHashMap";
259                }
260    
261                return callTranslator.createConstructorCallExpression(context.namer().kotlin(collectionClassName));
262            }
263        }
264    }