/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.resolve.constants; import com.google.common.collect.Sets; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.KtNodeTypes; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory; import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt; import org.jetbrains.kotlin.psi.KtConstantExpression; import org.jetbrains.kotlin.psi.KtElement; import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.KotlinTypeKt; import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.types.checker.KotlinTypeChecker; import java.util.Set; import static org.jetbrains.kotlin.diagnostics.Errors.*; public class CompileTimeConstantChecker { private static final Set<DiagnosticFactory<?>> errorsThatDependOnExpectedType = Sets.newHashSet(CONSTANT_EXPECTED_TYPE_MISMATCH, NULL_FOR_NONNULL_TYPE); private final KotlinBuiltIns builtIns; private final BindingTrace trace; private final boolean checkOnlyErrorsThatDependOnExpectedType; private final ResolutionContext<?> context; public CompileTimeConstantChecker( @NotNull ResolutionContext<?> context, @NotNull KotlinBuiltIns builtIns, boolean checkOnlyErrorsThatDependOnExpectedType ) { this.checkOnlyErrorsThatDependOnExpectedType = checkOnlyErrorsThatDependOnExpectedType; this.builtIns = builtIns; this.trace = context.trace; this.context = context; } // return true if there is an error public boolean checkConstantExpressionType( @Nullable ConstantValue<?> compileTimeConstant, @NotNull KtConstantExpression expression, @NotNull KotlinType expectedType ) { IElementType elementType = expression.getNode().getElementType(); if (elementType == KtNodeTypes.INTEGER_CONSTANT) { return checkIntegerValue(compileTimeConstant, expectedType, expression); } else if (elementType == KtNodeTypes.FLOAT_CONSTANT) { return checkFloatValue(compileTimeConstant, expectedType, expression); } else if (elementType == KtNodeTypes.BOOLEAN_CONSTANT) { return checkBooleanValue(expectedType, expression); } else if (elementType == KtNodeTypes.CHARACTER_CONSTANT) { return checkCharValue(compileTimeConstant, expectedType, expression); } else if (elementType == KtNodeTypes.NULL) { return checkNullValue(expectedType, expression); } return false; } private boolean checkIntegerValue( @Nullable ConstantValue<?> value, @NotNull KotlinType expectedType, @NotNull KtConstantExpression expression ) { if (value == null) { return reportError(INT_LITERAL_OUT_OF_RANGE.on(expression)); } if (expression.getText().endsWith("l")) { return reportError(WRONG_LONG_SUFFIX.on(expression)); } if (!noExpectedTypeOrError(expectedType)) { KotlinType valueType = value.getType(); if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(valueType, expectedType)) { return reportConstantExpectedTypeMismatch(expression, "integer", expectedType, null); } } return false; } private boolean checkFloatValue( @Nullable ConstantValue<?> value, @NotNull KotlinType expectedType, @NotNull KtConstantExpression expression ) { if (value == null) { return reportError(FLOAT_LITERAL_OUT_OF_RANGE.on(expression)); } if (!noExpectedTypeOrError(expectedType)) { KotlinType valueType = value.getType(); if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(valueType, expectedType)) { return reportConstantExpectedTypeMismatch(expression, "floating-point", expectedType, null); } } return false; } private boolean checkBooleanValue( @NotNull KotlinType expectedType, @NotNull KtConstantExpression expression ) { if (!noExpectedTypeOrError(expectedType) && !KotlinTypeChecker.DEFAULT.isSubtypeOf(builtIns.getBooleanType(), expectedType)) { return reportConstantExpectedTypeMismatch(expression, "boolean", expectedType, builtIns.getBooleanType()); } return false; } private boolean checkCharValue(ConstantValue<?> constant, KotlinType expectedType, KtConstantExpression expression) { if (!noExpectedTypeOrError(expectedType) && !KotlinTypeChecker.DEFAULT.isSubtypeOf(builtIns.getCharType(), expectedType)) { return reportConstantExpectedTypeMismatch(expression, "character", expectedType, builtIns.getCharType()); } if (constant != null) { return false; } Diagnostic diagnostic = parseCharacter(expression).getDiagnostic(); if (diagnostic != null) { return reportError(diagnostic); } return false; } private boolean checkNullValue(@NotNull KotlinType expectedType, @NotNull KtConstantExpression expression) { if (!noExpectedTypeOrError(expectedType) && !TypeUtils.acceptsNullable(expectedType)) { if (DiagnosticUtilsKt.reportTypeMismatchDueToTypeProjection(context, expression, expectedType, builtIns.getNullableNothingType())) { return true; } return reportError(NULL_FOR_NONNULL_TYPE.on(expression, expectedType)); } return false; } @NotNull private static CharacterWithDiagnostic parseCharacter(@NotNull KtConstantExpression expression) { String text = expression.getText(); // Strip the quotes if (text.length() < 2 || text.charAt(0) != '\'' || text.charAt(text.length() - 1) != '\'') { return createErrorCharacter(INCORRECT_CHARACTER_LITERAL.on(expression)); } text = text.substring(1, text.length() - 1); // now there're no quotes if (text.length() == 0) { return createErrorCharacter(EMPTY_CHARACTER_LITERAL.on(expression)); } if (text.charAt(0) != '\\') { // No escape if (text.length() == 1) { return new CharacterWithDiagnostic(text.charAt(0)); } return createErrorCharacter(TOO_MANY_CHARACTERS_IN_CHARACTER_LITERAL.on(expression, expression)); } return escapedStringToCharacter(text, expression); } @NotNull public static CharacterWithDiagnostic escapedStringToCharacter(@NotNull String text, @NotNull KtElement expression) { assert text.length() > 0 && text.charAt(0) == '\\' : "Only escaped sequences must be passed to this routine: " + text; // Escape String escape = text.substring(1); // strip the slash switch (escape.length()) { case 0: // bare slash return illegalEscape(expression); case 1: // one-char escape Character escaped = translateEscape(escape.charAt(0)); if (escaped == null) { return illegalEscape(expression); } return new CharacterWithDiagnostic(escaped); case 5: // unicode escape if (escape.charAt(0) == 'u') { try { Integer intValue = Integer.valueOf(escape.substring(1), 16); return new CharacterWithDiagnostic((char) intValue.intValue()); } catch (NumberFormatException e) { // Will be reported below } } break; } return illegalEscape(expression); } @NotNull private static CharacterWithDiagnostic illegalEscape(@NotNull KtElement expression) { return createErrorCharacter(ILLEGAL_ESCAPE.on(expression, expression)); } @NotNull private static CharacterWithDiagnostic createErrorCharacter(@NotNull Diagnostic diagnostic) { return new CharacterWithDiagnostic(diagnostic); } public static class CharacterWithDiagnostic { private Diagnostic diagnostic; private Character value; public CharacterWithDiagnostic(@NotNull Diagnostic diagnostic) { this.diagnostic = diagnostic; } public CharacterWithDiagnostic(char value) { this.value = value; } @Nullable public Diagnostic getDiagnostic() { return diagnostic; } @Nullable public Character getValue() { return value; } } @Nullable public static Character parseChar(@NotNull KtConstantExpression expression) { return parseCharacter(expression).getValue(); } @Nullable private static Character translateEscape(char c) { switch (c) { case 't': return '\t'; case 'b': return '\b'; case 'n': return '\n'; case 'r': return '\r'; case '\'': return '\''; case '\"': return '\"'; case '\\': return '\\'; case '$': return '$'; } return null; } private static boolean noExpectedTypeOrError(KotlinType expectedType) { return TypeUtils.noExpectedType(expectedType) || KotlinTypeKt.isError(expectedType); } private boolean reportConstantExpectedTypeMismatch( @NotNull KtConstantExpression expression, @NotNull String typeName, @NotNull KotlinType expectedType, @Nullable KotlinType expressionType ) { if (DiagnosticUtilsKt.reportTypeMismatchDueToTypeProjection(context, expression, expectedType, expressionType)) return true; trace.report(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, typeName, expectedType)); return true; } private boolean reportError(@NotNull Diagnostic diagnostic) { if (!checkOnlyErrorsThatDependOnExpectedType || errorsThatDependOnExpectedType.contains(diagnostic.getFactory())) { trace.report(diagnostic); return true; } return false; } }