001 /* 002 * Copyright 2010-2015 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.resolve.constants; 018 019 import com.google.common.collect.Sets; 020 import com.intellij.psi.tree.IElementType; 021 import org.jetbrains.annotations.NotNull; 022 import org.jetbrains.annotations.Nullable; 023 import org.jetbrains.kotlin.JetNodeTypes; 024 import org.jetbrains.kotlin.builtins.KotlinBuiltIns; 025 import org.jetbrains.kotlin.diagnostics.Diagnostic; 026 import org.jetbrains.kotlin.diagnostics.DiagnosticFactory; 027 import org.jetbrains.kotlin.psi.JetConstantExpression; 028 import org.jetbrains.kotlin.psi.JetElement; 029 import org.jetbrains.kotlin.resolve.BindingTrace; 030 import org.jetbrains.kotlin.types.JetType; 031 import org.jetbrains.kotlin.types.TypeUtils; 032 import org.jetbrains.kotlin.types.checker.JetTypeChecker; 033 034 import java.util.Set; 035 036 import static org.jetbrains.kotlin.diagnostics.Errors.*; 037 038 public class CompileTimeConstantChecker { 039 private static final Set<DiagnosticFactory<?>> errorsThatDependOnExpectedType = 040 Sets.<DiagnosticFactory<?>>newHashSet(CONSTANT_EXPECTED_TYPE_MISMATCH, NULL_FOR_NONNULL_TYPE); 041 042 private final KotlinBuiltIns builtIns; 043 private final BindingTrace trace; 044 private final boolean checkOnlyErrorsThatDependOnExpectedType; 045 046 public CompileTimeConstantChecker(@NotNull BindingTrace trace, boolean checkOnlyErrorsThatDependOnExpectedType) { 047 this.checkOnlyErrorsThatDependOnExpectedType = checkOnlyErrorsThatDependOnExpectedType; 048 this.builtIns = KotlinBuiltIns.getInstance(); 049 this.trace = trace; 050 } 051 052 // return true if there is an error 053 public boolean checkConstantExpressionType( 054 @Nullable CompileTimeConstant compileTimeConstant, 055 @NotNull JetConstantExpression expression, 056 @NotNull JetType expectedType 057 ) { 058 IElementType elementType = expression.getNode().getElementType(); 059 060 if (elementType == JetNodeTypes.INTEGER_CONSTANT) { 061 return checkIntegerValue(compileTimeConstant, expectedType, expression); 062 } 063 else if (elementType == JetNodeTypes.FLOAT_CONSTANT) { 064 return checkFloatValue(compileTimeConstant, expectedType, expression); 065 } 066 else if (elementType == JetNodeTypes.BOOLEAN_CONSTANT) { 067 return checkBooleanValue(expectedType, expression); 068 } 069 else if (elementType == JetNodeTypes.CHARACTER_CONSTANT) { 070 return checkCharValue(compileTimeConstant, expectedType, expression); 071 } 072 else if (elementType == JetNodeTypes.NULL) { 073 return checkNullValue(expectedType, expression); 074 } 075 return false; 076 } 077 078 private boolean checkIntegerValue( 079 @Nullable CompileTimeConstant value, 080 @NotNull JetType expectedType, 081 @NotNull JetConstantExpression expression 082 ) { 083 if (value == null) { 084 return reportError(INT_LITERAL_OUT_OF_RANGE.on(expression)); 085 } 086 087 if (expression.getText().endsWith("l")) { 088 return reportError(WRONG_LONG_SUFFIX.on(expression)); 089 } 090 091 if (!noExpectedTypeOrError(expectedType)) { 092 JetType valueType = value.getType(builtIns); 093 if (!JetTypeChecker.DEFAULT.isSubtypeOf(valueType, expectedType)) { 094 return reportError(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, "integer", expectedType)); 095 } 096 } 097 return false; 098 } 099 100 private boolean checkFloatValue( 101 @Nullable CompileTimeConstant value, 102 @NotNull JetType expectedType, 103 @NotNull JetConstantExpression expression 104 ) { 105 if (value == null) { 106 return reportError(FLOAT_LITERAL_OUT_OF_RANGE.on(expression)); 107 } 108 if (!noExpectedTypeOrError(expectedType)) { 109 JetType valueType = value.getType(builtIns); 110 if (!JetTypeChecker.DEFAULT.isSubtypeOf(valueType, expectedType)) { 111 return reportError(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, "floating-point", expectedType)); 112 } 113 } 114 return false; 115 } 116 117 private boolean checkBooleanValue( 118 @NotNull JetType expectedType, 119 @NotNull JetConstantExpression expression 120 ) { 121 if (!noExpectedTypeOrError(expectedType) 122 && !JetTypeChecker.DEFAULT.isSubtypeOf(builtIns.getBooleanType(), expectedType)) { 123 return reportError(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, "boolean", expectedType)); 124 } 125 return false; 126 } 127 128 private boolean checkCharValue(CompileTimeConstant<?> constant, JetType expectedType, JetConstantExpression expression) { 129 if (!noExpectedTypeOrError(expectedType) 130 && !JetTypeChecker.DEFAULT.isSubtypeOf(builtIns.getCharType(), expectedType)) { 131 return reportError(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, "character", expectedType)); 132 } 133 134 if (constant != null) { 135 return false; 136 } 137 138 Diagnostic diagnostic = parseCharacter(expression).getDiagnostic(); 139 if (diagnostic != null) { 140 return reportError(diagnostic); 141 } 142 return false; 143 } 144 145 private boolean checkNullValue(@NotNull JetType expectedType, @NotNull JetConstantExpression expression) { 146 if (!noExpectedTypeOrError(expectedType) && !TypeUtils.isNullableType(expectedType)) { 147 return reportError(NULL_FOR_NONNULL_TYPE.on(expression, expectedType)); 148 } 149 return false; 150 } 151 152 @NotNull 153 private static CharacterWithDiagnostic parseCharacter(@NotNull JetConstantExpression expression) { 154 String text = expression.getText(); 155 // Strip the quotes 156 if (text.length() < 2 || text.charAt(0) != '\'' || text.charAt(text.length() - 1) != '\'') { 157 return createErrorCharacter(INCORRECT_CHARACTER_LITERAL.on(expression)); 158 } 159 text = text.substring(1, text.length() - 1); // now there're no quotes 160 161 if (text.length() == 0) { 162 return createErrorCharacter(EMPTY_CHARACTER_LITERAL.on(expression)); 163 } 164 165 if (text.charAt(0) != '\\') { 166 // No escape 167 if (text.length() == 1) { 168 return new CharacterWithDiagnostic(text.charAt(0)); 169 } 170 return createErrorCharacter(TOO_MANY_CHARACTERS_IN_CHARACTER_LITERAL.on(expression, expression)); 171 } 172 return escapedStringToCharacter(text, expression); 173 } 174 175 @NotNull 176 public static CharacterWithDiagnostic escapedStringToCharacter(@NotNull String text, @NotNull JetElement expression) { 177 assert text.length() > 0 && text.charAt(0) == '\\' : "Only escaped sequences must be passed to this routine: " + text; 178 179 // Escape 180 String escape = text.substring(1); // strip the slash 181 switch (escape.length()) { 182 case 0: 183 // bare slash 184 return illegalEscape(expression); 185 case 1: 186 // one-char escape 187 Character escaped = translateEscape(escape.charAt(0)); 188 if (escaped == null) { 189 return illegalEscape(expression); 190 } 191 return new CharacterWithDiagnostic(escaped); 192 case 5: 193 // unicode escape 194 if (escape.charAt(0) == 'u') { 195 try { 196 Integer intValue = Integer.valueOf(escape.substring(1), 16); 197 return new CharacterWithDiagnostic((char) intValue.intValue()); 198 } catch (NumberFormatException e) { 199 // Will be reported below 200 } 201 } 202 break; 203 } 204 return illegalEscape(expression); 205 } 206 207 @NotNull 208 private static CharacterWithDiagnostic illegalEscape(@NotNull JetElement expression) { 209 return createErrorCharacter(ILLEGAL_ESCAPE.on(expression, expression)); 210 } 211 212 @NotNull 213 private static CharacterWithDiagnostic createErrorCharacter(@NotNull Diagnostic diagnostic) { 214 return new CharacterWithDiagnostic(diagnostic); 215 } 216 217 public static class CharacterWithDiagnostic { 218 private Diagnostic diagnostic; 219 private Character value; 220 221 public CharacterWithDiagnostic(@NotNull Diagnostic diagnostic) { 222 this.diagnostic = diagnostic; 223 } 224 225 public CharacterWithDiagnostic(char value) { 226 this.value = value; 227 } 228 229 @Nullable 230 public Diagnostic getDiagnostic() { 231 return diagnostic; 232 } 233 234 @Nullable 235 public Character getValue() { 236 return value; 237 } 238 } 239 240 @Nullable 241 public static Character parseChar(@NotNull JetConstantExpression expression) { 242 return parseCharacter(expression).getValue(); 243 } 244 245 @Nullable 246 private static Character translateEscape(char c) { 247 switch (c) { 248 case 't': 249 return '\t'; 250 case 'b': 251 return '\b'; 252 case 'n': 253 return '\n'; 254 case 'r': 255 return '\r'; 256 case '\'': 257 return '\''; 258 case '\"': 259 return '\"'; 260 case '\\': 261 return '\\'; 262 case '$': 263 return '$'; 264 } 265 return null; 266 } 267 268 public static boolean noExpectedTypeOrError(JetType expectedType) { 269 return TypeUtils.noExpectedType(expectedType) || expectedType.isError(); 270 } 271 272 private boolean reportError(@NotNull Diagnostic diagnostic) { 273 if (!checkOnlyErrorsThatDependOnExpectedType || errorsThatDependOnExpectedType.contains(diagnostic.getFactory())) { 274 trace.report(diagnostic); 275 return true; 276 } 277 return false; 278 } 279 }