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