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