001    /*
002     * Copyright 2010-2016 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.expression;
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.kotlin.KtNodeTypes;
023    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
024    import org.jetbrains.kotlin.builtins.PrimitiveType;
025    import org.jetbrains.kotlin.builtins.ReflectionTypes;
026    import org.jetbrains.kotlin.descriptors.CallableDescriptor;
027    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
028    import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
029    import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
030    import org.jetbrains.kotlin.js.patterns.NamePredicate;
031    import org.jetbrains.kotlin.js.patterns.typePredicates.TypePredicatesKt;
032    import org.jetbrains.kotlin.js.translate.context.Namer;
033    import org.jetbrains.kotlin.js.translate.context.TemporaryVariable;
034    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
035    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
036    import org.jetbrains.kotlin.js.translate.general.Translation;
037    import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.TopLevelFIF;
038    import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
039    import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
040    import org.jetbrains.kotlin.lexer.KtTokens;
041    import org.jetbrains.kotlin.name.Name;
042    import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS;
043    import org.jetbrains.kotlin.psi.KtExpression;
044    import org.jetbrains.kotlin.psi.KtIsExpression;
045    import org.jetbrains.kotlin.psi.KtTypeReference;
046    import org.jetbrains.kotlin.resolve.DescriptorUtils;
047    import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
048    import org.jetbrains.kotlin.types.DynamicTypesKt;
049    import org.jetbrains.kotlin.types.KotlinType;
050    import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
051    
052    import java.util.Collections;
053    
054    import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionTypeOrSubtype;
055    import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isAnyOrNullableAny;
056    import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isArray;
057    import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt.getNameIfStandardType;
058    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeByReference;
059    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeForExpression;
060    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.equality;
061    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated;
062    import static org.jetbrains.kotlin.psi.KtPsiUtil.findChildByType;
063    import static org.jetbrains.kotlin.types.TypeUtils.*;
064    
065    public final class PatternTranslator extends AbstractTranslator {
066    
067        @NotNull
068        public static PatternTranslator newInstance(@NotNull TranslationContext context) {
069            return new PatternTranslator(context);
070        }
071    
072        private PatternTranslator(@NotNull TranslationContext context) {
073            super(context);
074        }
075    
076        public static boolean isCastExpression(@NotNull KtBinaryExpressionWithTypeRHS expression) {
077            return isSafeCast(expression) || isUnsafeCast(expression);
078        }
079    
080        @NotNull
081        public JsExpression translateCastExpression(@NotNull KtBinaryExpressionWithTypeRHS expression) {
082            assert isCastExpression(expression): "Expected cast expression, got " + expression;
083            KtExpression left = expression.getLeft();
084            JsExpression expressionToCast = Translation.translateAsExpression(left, context());
085    
086            TemporaryVariable temporary = context().declareTemporary(expressionToCast);
087    
088            KtTypeReference typeReference = expression.getRight();
089            assert typeReference != null: "Cast expression must have type reference";
090            KotlinType sourceType = getTypeForExpression(bindingContext(), left);
091            JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), sourceType, typeReference);
092            if (isCheck == null) return expressionToCast;
093    
094            JsExpression onFail;
095    
096            if (isSafeCast(expression)) {
097                onFail = JsLiteral.NULL;
098            }
099            else {
100                JsExpression throwCCEFunRef = context().namer().throwClassCastExceptionFunRef();
101                onFail = new JsInvocation(throwCCEFunRef);
102            }
103    
104            return new JsConditional(isCheck, temporary.reference(), onFail);
105        }
106    
107        @NotNull
108        public JsExpression translateIsExpression(@NotNull KtIsExpression expression) {
109            JsExpression left = Translation.translateAsExpression(expression.getLeftHandSide(), context());
110            KtTypeReference typeReference = expression.getTypeReference();
111            assert typeReference != null;
112            KotlinType sourceType = getTypeForExpression(bindingContext(), expression.getLeftHandSide());
113            JsExpression result = translateIsCheck(left, sourceType, typeReference);
114            if (result == null) return JsLiteral.getBoolean(!expression.isNegated());
115    
116            if (expression.isNegated()) {
117                return negated(result);
118            }
119            return result;
120        }
121    
122        @Nullable
123        public JsExpression translateIsCheck(@NotNull JsExpression subject, @Nullable KotlinType sourceType,
124                @NotNull KtTypeReference targetTypeReference) {
125            KotlinType targetType = getTypeByReference(bindingContext(), targetTypeReference);
126            if (sourceType != null && !DynamicTypesKt.isDynamic(sourceType) && TypeUtilsKt.isSubtypeOf(sourceType, targetType)) return null;
127    
128            JsExpression checkFunReference = doGetIsTypeCheckCallable(targetType);
129            boolean isReifiedType = isReifiedTypeParameter(targetType);
130            if (!isReifiedType && isNullableType(targetType) ||
131                isReifiedType && findChildByType(targetTypeReference, KtNodeTypes.NULLABLE_TYPE) != null
132            ) {
133                checkFunReference = namer().orNull(checkFunReference);
134            }
135    
136            return new JsInvocation(checkFunReference, subject);
137        }
138    
139        @NotNull
140        public JsExpression getIsTypeCheckCallable(@NotNull KotlinType type) {
141            JsExpression callable = doGetIsTypeCheckCallable(type);
142    
143            if (isNullableType(type)) {
144                return namer().orNull(callable);
145            }
146    
147            return callable;
148        }
149    
150        @NotNull
151        private JsExpression doGetIsTypeCheckCallable(@NotNull KotlinType type) {
152            JsExpression builtinCheck = getIsTypeCheckCallableForBuiltin(type);
153            if (builtinCheck != null) return builtinCheck;
154    
155            builtinCheck = getIsTypeCheckCallableForPrimitiveBuiltin(type);
156            if (builtinCheck != null) return builtinCheck;
157    
158            TypeParameterDescriptor typeParameterDescriptor = getTypeParameterDescriptorOrNull(type);
159            if (typeParameterDescriptor != null) {
160                if (typeParameterDescriptor.isReified()) {
161                    return getIsTypeCheckCallableForReifiedType(typeParameterDescriptor);
162                }
163    
164                JsExpression result = null;
165                for (KotlinType upperBound : typeParameterDescriptor.getUpperBounds()) {
166                    JsExpression next = doGetIsTypeCheckCallable(upperBound);
167                    result = result != null ? namer().andPredicate(result, next) : next;
168                }
169                assert result != null : "KotlinType is expected to return at least one upper bound: " + type;
170                return result;
171            }
172    
173            JsNameRef typeName = getClassNameReference(type);
174            return namer().isInstanceOf(typeName);
175        }
176    
177        @Nullable
178        private JsExpression getIsTypeCheckCallableForBuiltin(@NotNull KotlinType type) {
179            if (isAnyOrNullableAny(type)) return namer().isAny();
180    
181            if (isFunctionTypeOrSubtype(type) && !ReflectionTypes.isNumberedKPropertyOrKMutablePropertyType(type)) {
182                return namer().isTypeOf(program().getStringLiteral("function"));
183            }
184    
185            if (isArray(type)) return Namer.IS_ARRAY_FUN_REF;
186    
187            if (TypePredicatesKt.getCHAR_SEQUENCE().apply(type)) return namer().isCharSequence();
188    
189            if (TypePredicatesKt.getCOMPARABLE().apply(type)) return namer().isComparable();
190    
191            return null;
192        }
193    
194        @Nullable
195        private JsExpression getIsTypeCheckCallableForPrimitiveBuiltin(@NotNull KotlinType type) {
196            Name typeName = getNameIfStandardType(type);
197    
198            if (NamePredicate.STRING.apply(typeName)) {
199                return namer().isTypeOf(program().getStringLiteral("string"));
200            }
201    
202            if (NamePredicate.BOOLEAN.apply(typeName)) {
203                return namer().isTypeOf(program().getStringLiteral("boolean"));
204            }
205    
206            if (NamePredicate.LONG.apply(typeName)) {
207                return namer().isInstanceOf(Namer.kotlinLong());
208            }
209    
210            if (NamePredicate.NUMBER.apply(typeName)) {
211                return namer().kotlin(Namer.IS_NUMBER);
212            }
213    
214            if (NamePredicate.CHAR.apply(typeName)) {
215                return namer().kotlin(Namer.IS_CHAR);
216            }
217    
218            if (NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS.apply(typeName)) {
219                return namer().isTypeOf(program().getStringLiteral("number"));
220            }
221    
222            return null;
223        }
224    
225        @NotNull
226        private JsExpression getIsTypeCheckCallableForReifiedType(@NotNull TypeParameterDescriptor typeParameter) {
227            assert typeParameter.isReified(): "Expected reified type, actual: " + typeParameter;
228            DeclarationDescriptor containingDeclaration = typeParameter.getContainingDeclaration();
229            assert containingDeclaration instanceof CallableDescriptor:
230                    "Expected type parameter " + typeParameter +
231                    " to be contained in CallableDescriptor, actual: " + containingDeclaration.getClass();
232    
233            JsExpression alias = context().getAliasForDescriptor(typeParameter);
234            assert alias != null: "No alias found for reified type parameter: " + typeParameter;
235            return alias;
236        }
237    
238        @NotNull
239        private JsNameRef getClassNameReference(@NotNull KotlinType type) {
240            ClassDescriptor referencedClass = DescriptorUtils.getClassDescriptorForType(type);
241            return context().getQualifiedReference(referencedClass);
242        }
243    
244        @NotNull
245        public JsExpression translateExpressionPattern(
246                @NotNull KotlinType type,
247                @NotNull JsExpression expressionToMatch,
248                @NotNull KtExpression patternExpression
249        ) {
250            JsExpression expressionToMatchAgainst = translateExpressionForExpressionPattern(patternExpression);
251            KotlinType patternType = BindingUtils.getTypeForExpression(bindingContext(), patternExpression);
252    
253            EqualityType matchEquality = equalityType(type);
254            EqualityType patternEquality = equalityType(patternType);
255    
256            if (matchEquality == EqualityType.PRIMITIVE && patternEquality == EqualityType.PRIMITIVE) {
257                return equality(expressionToMatch, expressionToMatchAgainst);
258            }
259            else if (expressionToMatchAgainst == JsLiteral.NULL) {
260                return TranslationUtils.nullCheck(expressionToMatch, false);
261            }
262            else {
263                return TopLevelFIF.KOTLIN_EQUALS.apply(expressionToMatch, Collections.singletonList(expressionToMatchAgainst), context());
264            }
265        }
266    
267        @NotNull
268        private static EqualityType equalityType(@NotNull KotlinType type) {
269            DeclarationDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
270            if (!(descriptor instanceof ClassDescriptor)) return EqualityType.GENERAL;
271    
272            PrimitiveType primitive = KotlinBuiltIns.getPrimitiveTypeByFqName(DescriptorUtilsKt.getFqNameUnsafe(descriptor));
273            if (primitive == null) return EqualityType.GENERAL;
274    
275            return primitive == PrimitiveType.LONG ? EqualityType.LONG : EqualityType.PRIMITIVE;
276        }
277    
278        private enum EqualityType {
279            PRIMITIVE,
280            LONG,
281            GENERAL
282        }
283    
284        @NotNull
285        public JsExpression translateExpressionForExpressionPattern(@NotNull KtExpression patternExpression) {
286            return Translation.translateAsExpression(patternExpression, context());
287        }
288    
289        private static boolean isSafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
290            return expression.getOperationReference().getReferencedNameElementType() == KtTokens.AS_SAFE;
291        }
292    
293        private static boolean isUnsafeCast(@NotNull KtBinaryExpressionWithTypeRHS expression) {
294            return expression.getOperationReference().getReferencedNameElementType() == KtTokens.AS_KEYWORD;
295        }
296    }