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