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 }