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