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 }