001    /*
002     * Copyright 2010-2013 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.jet.lang.resolve.constants;
018    
019    import com.google.common.base.Function;
020    import com.google.common.collect.Sets;
021    import com.intellij.psi.tree.IElementType;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.jet.JetNodeTypes;
025    import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory;
026    import org.jetbrains.jet.lang.diagnostics.Diagnostic;
027    import org.jetbrains.jet.lang.diagnostics.rendering.DefaultErrorMessages;
028    import org.jetbrains.jet.lang.psi.JetConstantExpression;
029    import org.jetbrains.jet.lang.psi.JetElement;
030    import org.jetbrains.jet.lang.types.JetType;
031    import org.jetbrains.jet.lang.types.TypeConstructor;
032    import org.jetbrains.jet.lang.types.TypeUtils;
033    import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
034    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
035    
036    import java.util.Set;
037    
038    import static org.jetbrains.jet.lang.diagnostics.Errors.*;
039    
040    public class CompileTimeConstantResolver {
041        private final KotlinBuiltIns builtIns;
042    
043        public CompileTimeConstantResolver() {
044            this.builtIns = KotlinBuiltIns.getInstance();
045        }
046    
047        @Nullable
048        public Diagnostic checkConstantExpressionType(
049                @NotNull JetConstantExpression expression,
050                @NotNull JetType expectedType
051        ) {
052            CompileTimeConstant<?> compileTimeConstant = getCompileTimeConstant(expression, expectedType);
053            Set<DiagnosticFactory> errorsThatDependOnExpectedType =
054                    Sets.<DiagnosticFactory>newHashSet(CONSTANT_EXPECTED_TYPE_MISMATCH, NULL_FOR_NONNULL_TYPE);
055    
056            if (compileTimeConstant instanceof ErrorValueWithDiagnostic) {
057                Diagnostic diagnostic = ((ErrorValueWithDiagnostic) compileTimeConstant).getDiagnostic();
058                if (errorsThatDependOnExpectedType.contains(diagnostic.getFactory())) {
059                    return diagnostic;
060                }
061            }
062            return null;
063        }
064    
065        @NotNull
066        public CompileTimeConstant<?> getCompileTimeConstant(
067                @NotNull JetConstantExpression expression,
068                @NotNull JetType expectedType
069        ) {
070            IElementType elementType = expression.getNode().getElementType();
071    
072            CompileTimeConstant<?> value;
073            if (elementType == JetNodeTypes.INTEGER_CONSTANT) {
074                value = getIntegerValue(expression, expectedType);
075            }
076            else if (elementType == JetNodeTypes.FLOAT_CONSTANT) {
077                value = getFloatValue(expression, expectedType);
078            }
079            else if (elementType == JetNodeTypes.BOOLEAN_CONSTANT) {
080                value = getBooleanValue(expression, expectedType);
081            }
082            else if (elementType == JetNodeTypes.CHARACTER_CONSTANT) {
083                value = getCharValue(expression, expectedType);
084            }
085            else if (elementType == JetNodeTypes.NULL) {
086                value = getNullValue(expression, expectedType);
087            }
088            else {
089                throw new IllegalArgumentException("Unsupported constant: " + expression);
090            }
091            return value;
092        }
093    
094        @NotNull
095        public CompileTimeConstant<?> getIntegerValue(
096                @NotNull JetConstantExpression expression, @NotNull JetType expectedType
097        ) {
098            String text = expression.getText();
099            return getIntegerValue(parseLongValue(text), expectedType, expression);
100        }
101    
102        @NotNull
103        public CompileTimeConstant<?> getIntegerValue(
104                @Nullable Long value,
105                @NotNull JetType expectedType,
106                @NotNull JetConstantExpression expression
107        ) {
108            if (value == null) {
109                return createErrorValue(INT_LITERAL_OUT_OF_RANGE.on(expression));
110            }
111            if (noExpectedTypeOrError(expectedType)) {
112                if (Integer.MIN_VALUE <= value && value <= Integer.MAX_VALUE) {
113                    return new IntValue(value.intValue());
114                }
115                return new LongValue(value);
116            }
117            Function<Long, ? extends CompileTimeConstant<?>> create;
118            long lowerBound;
119            long upperBound;
120            TypeConstructor constructor = expectedType.getConstructor();
121            if (constructor == builtIns.getInt().getTypeConstructor()) {
122                create = IntValue.CREATE;
123                lowerBound = Integer.MIN_VALUE;
124                upperBound = Integer.MAX_VALUE;
125            }
126            else if (constructor == builtIns.getLong().getTypeConstructor()) {
127                create = LongValue.CREATE;
128                lowerBound = Long.MIN_VALUE;
129                upperBound = Long.MAX_VALUE;
130            }
131            else if (constructor == builtIns.getShort().getTypeConstructor()) {
132                create = ShortValue.CREATE;
133                lowerBound = Short.MIN_VALUE;
134                upperBound = Short.MAX_VALUE;
135            }
136            else if (constructor == builtIns.getByte().getTypeConstructor()) {
137                create = ByteValue.CREATE;
138                lowerBound = Byte.MIN_VALUE;
139                upperBound = Byte.MAX_VALUE;
140            }
141            else  {
142                JetTypeChecker typeChecker = JetTypeChecker.INSTANCE;
143                JetType intType = builtIns.getIntType();
144                JetType longType = builtIns.getLongType();
145                if (typeChecker.isSubtypeOf(intType, expectedType)) {
146                    return getIntegerValue(value, intType, expression);
147                }
148                else if (typeChecker.isSubtypeOf(longType, expectedType)) {
149                    return getIntegerValue(value, longType, expression);
150                }
151                else {
152                    return createErrorValue(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, "integer", expectedType));
153                }
154            }
155    
156            if (value != null && lowerBound <= value && value <= upperBound) {
157                return create.apply(value);
158            }
159            return createErrorValue(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, "integer", expectedType));
160        }
161    
162        @Nullable
163        public static Long parseLongValue(String text) {
164            try {
165                long value;
166                if (text.startsWith("0x") || text.startsWith("0X")) {
167                    String hexString = text.substring(2);
168                    value = Long.parseLong(hexString, 16);
169                }
170                else if (text.startsWith("0b") || text.startsWith("0B")) {
171                    String binString = text.substring(2);
172                    value = Long.parseLong(binString, 2);
173                }
174                else {
175                    value = Long.parseLong(text);
176                }
177                return value;
178            }
179            catch (NumberFormatException e) {
180                return null;
181            }
182        }
183    
184        @Nullable
185        public static Double parseDoubleValue(String text) {
186            try {
187                return Double.parseDouble(text);
188            }
189            catch (NumberFormatException e) {
190                return null;
191            }
192        }
193    
194        @NotNull
195        public CompileTimeConstant<?> getFloatValue(
196                @NotNull JetConstantExpression expression, @NotNull JetType expectedType
197        ) {
198            String text = expression.getText();
199            try {
200                if (noExpectedTypeOrError(expectedType)
201                    || JetTypeChecker.INSTANCE.isSubtypeOf(builtIns.getDoubleType(), expectedType)) {
202                    return new DoubleValue(Double.parseDouble(text));
203                }
204                else if (JetTypeChecker.INSTANCE.isSubtypeOf(builtIns.getFloatType(), expectedType)) {
205                    return new FloatValue(Float.parseFloat(text));
206                }
207                else {
208                    return createErrorValue(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, "floating-point", expectedType));
209                }
210            }
211            catch (NumberFormatException e) {
212                return createErrorValue(FLOAT_LITERAL_OUT_OF_RANGE.on(expression));
213            }
214        }
215    
216        @Nullable
217        private static CompileTimeConstant<?> checkNativeType(
218                JetType expectedType,
219                String title,
220                JetType nativeType,
221                JetConstantExpression expression
222        ) {
223            if (!noExpectedTypeOrError(expectedType)
224                && !JetTypeChecker.INSTANCE.isSubtypeOf(nativeType, expectedType)) {
225    
226                return createErrorValue(CONSTANT_EXPECTED_TYPE_MISMATCH.on(expression, title, expectedType));
227            }
228            return null;
229        }
230    
231        @NotNull
232        public CompileTimeConstant<?> getBooleanValue(
233                @NotNull JetConstantExpression expression, @NotNull JetType expectedType
234        ) {
235            String text = expression.getText();
236            CompileTimeConstant<?> error = checkNativeType(expectedType, "boolean", builtIns.getBooleanType(), expression);
237            if (error != null) {
238                return error;
239            }
240            if ("true".equals(text)) {
241                return BooleanValue.TRUE;
242            }
243            else if ("false".equals(text)) {
244                return BooleanValue.FALSE;
245            }
246            throw new IllegalStateException("Must not happen. A boolean literal has text: " + text);
247        }
248    
249        @NotNull
250        public CompileTimeConstant<?> getCharValue(
251                @NotNull JetConstantExpression expression, @NotNull JetType expectedType
252        ) {
253            String text = expression.getText();
254            CompileTimeConstant<?> error = checkNativeType(expectedType, "character", builtIns.getCharType(), expression);
255            if (error != null) {
256                return error;
257            }
258    
259            // Strip the quotes
260            if (text.length() < 2 || text.charAt(0) != '\'' || text.charAt(text.length() - 1) != '\'') {
261                return createErrorValue(INCORRECT_CHARACTER_LITERAL.on(expression));
262            }
263            text = text.substring(1, text.length() - 1); // now there're no quotes
264    
265            if (text.length() == 0) {
266                return createErrorValue(EMPTY_CHARACTER_LITERAL.on(expression));
267            }
268    
269            if (text.charAt(0) != '\\') {
270                // No escape
271                if (text.length() == 1) {
272                    return new CharValue(text.charAt(0));
273                }
274                return createErrorValue(TOO_MANY_CHARACTERS_IN_CHARACTER_LITERAL.on(expression, expression));
275            }
276            return escapedStringToCharValue(text, expression);
277        }
278    
279        @NotNull
280        public static CompileTimeConstant<?> escapedStringToCharValue(
281                @NotNull String text,
282                @NotNull JetElement expression
283        ) {
284            assert text.length() > 0 && text.charAt(0) == '\\' : "Only escaped sequences must be passed to this routine: " + text;
285    
286            // Escape
287            String escape = text.substring(1); // strip the slash
288            switch (escape.length()) {
289                case 0:
290                    // bare slash
291                    return illegalEscape(expression);
292                case 1:
293                    // one-char escape
294                    Character escaped = translateEscape(escape.charAt(0));
295                    if (escaped == null) {
296                        return illegalEscape(expression);
297                    }
298                    return new CharValue(escaped);
299                case 5:
300                    // unicode escape
301                    if (escape.charAt(0) == 'u') {
302                        try {
303                            Integer intValue = Integer.valueOf(escape.substring(1), 16);
304                            return new CharValue((char) intValue.intValue());
305                        } catch (NumberFormatException e) {
306                            // Will be reported below
307                        }
308                    }
309                    break;
310            }
311            return illegalEscape(expression);
312        }
313    
314        @NotNull
315        private static CompileTimeConstant<?> illegalEscape(@NotNull JetElement expression) {
316            return createErrorValue(ILLEGAL_ESCAPE.on(expression, expression));
317        }
318    
319        @Nullable
320        public static Character translateEscape(char c) {
321            switch (c) {
322                case 't':
323                    return '\t';
324                case 'b':
325                    return '\b';
326                case 'n':
327                    return '\n';
328                case 'r':
329                    return '\r';
330                case '\'':
331                    return '\'';
332                case '\"':
333                    return '\"';
334                case '\\':
335                    return '\\';
336                case '$':
337                    return '$';
338            }
339            return null;
340        }
341    
342        @NotNull
343        public static CompileTimeConstant<?> getNullValue(@NotNull JetConstantExpression expression, @NotNull JetType expectedType) {
344            if (noExpectedTypeOrError(expectedType) || expectedType.isNullable()) {
345                return NullValue.NULL;
346            }
347            return createErrorValue(NULL_FOR_NONNULL_TYPE.on(expression, expectedType));
348        }
349    
350        private static boolean noExpectedTypeOrError(JetType expectedType) {
351            return TypeUtils.noExpectedType(expectedType) || expectedType.isError();
352        }
353    
354        @NotNull
355        private static ErrorValue createErrorValue(@NotNull Diagnostic diagnostic) {
356            return new ErrorValueWithDiagnostic(diagnostic);
357        }
358    
359        public static class ErrorValueWithDiagnostic extends ErrorValue {
360            private final Diagnostic diagnostic;
361    
362            public ErrorValueWithDiagnostic(@NotNull Diagnostic diagnostic) {
363                this.diagnostic = diagnostic;
364            }
365    
366            @NotNull
367            public Diagnostic getDiagnostic() {
368                return diagnostic;
369            }
370    
371            @NotNull
372            @Override
373            public JetType getType(@NotNull KotlinBuiltIns kotlinBuiltIns) {
374                throw new UnsupportedOperationException();
375            }
376    
377            @Override
378            public String toString() {
379                return DefaultErrorMessages.RENDERER.render(diagnostic);
380            }
381        }
382    }