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(KotlinBuiltIns.getInstance());
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(KotlinBuiltIns.getInstance());
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    }