001    /*
002     * Copyright 2010-2015 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.kotlin.js.translate.reference;
018    
019    import com.google.dart.compiler.backend.js.ast.JsExpression;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
023    import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
024    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
025    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
026    import org.jetbrains.kotlin.js.translate.general.Translation;
027    import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
028    import org.jetbrains.kotlin.psi.KtArrayAccessExpression;
029    import org.jetbrains.kotlin.psi.KtExpression;
030    import org.jetbrains.kotlin.psi.ValueArgument;
031    import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument;
032    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
033    import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
034    
035    import java.util.Collections;
036    import java.util.HashMap;
037    import java.util.List;
038    import java.util.Map;
039    
040    public class ArrayAccessTranslator extends AbstractTranslator implements AccessTranslator {
041    
042        /*package*/
043        static ArrayAccessTranslator newInstance(@NotNull KtArrayAccessExpression expression,
044                                                 @NotNull TranslationContext context) {
045            return new ArrayAccessTranslator(expression, context);
046        }
047    
048        @NotNull
049        private final KtArrayAccessExpression expression;
050    
051        private ArrayAccessTranslator(@NotNull KtArrayAccessExpression expression, @NotNull TranslationContext context) {
052            super(context);
053            this.expression = expression;
054        }
055    
056        @NotNull
057        @Override
058        public JsExpression translateAsGet() {
059            return translateAsGet(getArrayExpression());
060        }
061    
062        @NotNull
063        protected JsExpression translateAsGet(@NotNull JsExpression arrayExpression) {
064            return translateAsMethodCall(arrayExpression,  null);
065        }
066    
067        @NotNull
068        @Override
069        public JsExpression translateAsSet(@NotNull JsExpression setTo) {
070            return translateAsMethodCall(getArrayExpression(), setTo);
071        }
072    
073        @NotNull
074        private JsExpression translateAsMethodCall(@NotNull JsExpression arrayExpression, @Nullable JsExpression toSetTo) {
075            boolean isGetter = toSetTo == null;
076            TranslationContext context = context();
077            ResolvedCall<FunctionDescriptor> resolvedCall = BindingUtils.getResolvedCallForArrayAccess(bindingContext(), expression, isGetter);
078            if (!isGetter) {
079                context = contextWithValueParameterAliasInArrayGetAccess(toSetTo);
080            }
081            return CallTranslator.translate(context, resolvedCall, arrayExpression);
082        }
083    
084        @NotNull
085        protected JsExpression getArrayExpression() {
086            KtExpression arrayExpression = expression.getArrayExpression();
087            assert arrayExpression != null : "Code with parsing errors shouldn't be translated";
088            return Translation.translateAsExpression(arrayExpression, context());
089        }
090    
091        // this is hack for a[b]++ -> a.set(b, a.get(b) + 1). Frontend generate fake expression for a.get(b) + 1.
092        @NotNull
093        private TranslationContext contextWithValueParameterAliasInArrayGetAccess(@NotNull JsExpression toSetTo) {
094            ResolvedCall<FunctionDescriptor> resolvedCall =
095                    BindingUtils.getResolvedCallForArrayAccess(bindingContext(), expression,  /*isGetter = */ false);
096    
097            List<ResolvedValueArgument> arguments = resolvedCall.getValueArgumentsByIndex();
098            if (arguments == null) {
099                throw new IllegalStateException("Failed to arrange value arguments by index: " + resolvedCall.getResultingDescriptor());
100            }
101            ResolvedValueArgument lastArgument = arguments.get(arguments.size() - 1);
102            assert lastArgument instanceof ExpressionValueArgument:
103                    "Last argument of array-like setter must be ExpressionValueArgument: " + lastArgument;
104    
105            ValueArgument valueArgument = ((ExpressionValueArgument) lastArgument).getValueArgument();
106            assert valueArgument != null;
107    
108            KtExpression element = valueArgument.getArgumentExpression();
109            return context().innerContextWithAliasesForExpressions(Collections.singletonMap(element, toSetTo));
110        }
111    
112        @NotNull
113        @Override
114        public AccessTranslator getCached() {
115            Map<KtExpression, JsExpression> aliases = new HashMap<KtExpression, JsExpression>();
116    
117            JsExpression arrayExpression = context().cacheExpressionIfNeeded(getArrayExpression());
118            aliases.put(expression.getArrayExpression(), arrayExpression);
119    
120            for (KtExpression ktExpression : expression.getIndexExpressions()) {
121                JsExpression jsExpression = context().cacheExpressionIfNeeded(Translation.translateAsExpression(ktExpression, context()));
122                aliases.put(ktExpression, jsExpression);
123            }
124    
125            return new CachedArrayAccessTranslator(expression, context().innerContextWithAliasesForExpressions(aliases), arrayExpression);
126        }
127    
128        private static class CachedArrayAccessTranslator extends ArrayAccessTranslator  {
129            @NotNull
130            private final JsExpression arrayExpression;
131    
132            protected CachedArrayAccessTranslator(
133                    @NotNull KtArrayAccessExpression expression,
134                    @NotNull TranslationContext context,
135                    @NotNull JsExpression arrayExpression
136            ) {
137                super(expression, context);
138                this.arrayExpression = arrayExpression;
139            }
140    
141            @NotNull
142            @Override
143            protected JsExpression getArrayExpression() {
144                return arrayExpression;
145            }
146    
147            @NotNull
148            @Override
149            public AccessTranslator getCached() {
150                return this;
151            }
152        }
153    }