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 ConstantValue<?> 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 ConstantValue<?> 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();
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 ConstantValue<?> 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();
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(ConstantValue<?> 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.acceptsNullable(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 }