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 }