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