Introduce language feature: underscores in numeric literals
#KT-2964 Fixed
This commit is contained in:
@@ -723,6 +723,7 @@ public interface Errors {
|
|||||||
DiagnosticFactory2<KtConstantExpression, String, KotlinType> CONSTANT_EXPECTED_TYPE_MISMATCH = DiagnosticFactory2.create(ERROR);
|
DiagnosticFactory2<KtConstantExpression, String, KotlinType> CONSTANT_EXPECTED_TYPE_MISMATCH = DiagnosticFactory2.create(ERROR);
|
||||||
DiagnosticFactory0<KtConstantExpression> INCORRECT_CHARACTER_LITERAL = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<KtConstantExpression> INCORRECT_CHARACTER_LITERAL = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory0<KtConstantExpression> EMPTY_CHARACTER_LITERAL = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<KtConstantExpression> EMPTY_CHARACTER_LITERAL = DiagnosticFactory0.create(ERROR);
|
||||||
|
DiagnosticFactory0<KtConstantExpression> ILLEGAL_UNDERSCORE = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory1<KtConstantExpression, KtConstantExpression> TOO_MANY_CHARACTERS_IN_CHARACTER_LITERAL = DiagnosticFactory1.create(ERROR);
|
DiagnosticFactory1<KtConstantExpression, KtConstantExpression> TOO_MANY_CHARACTERS_IN_CHARACTER_LITERAL = DiagnosticFactory1.create(ERROR);
|
||||||
DiagnosticFactory1<KtElement, KtElement> ILLEGAL_ESCAPE = DiagnosticFactory1.create(ERROR, CUT_CHAR_QUOTES);
|
DiagnosticFactory1<KtElement, KtElement> ILLEGAL_ESCAPE = DiagnosticFactory1.create(ERROR, CUT_CHAR_QUOTES);
|
||||||
DiagnosticFactory1<KtConstantExpression, KotlinType> NULL_FOR_NONNULL_TYPE = DiagnosticFactory1.create(ERROR);
|
DiagnosticFactory1<KtConstantExpression, KotlinType> NULL_FOR_NONNULL_TYPE = DiagnosticFactory1.create(ERROR);
|
||||||
|
|||||||
+1
@@ -454,6 +454,7 @@ public class DefaultErrorMessages {
|
|||||||
MAP.put(FLOAT_LITERAL_OUT_OF_RANGE, "The value is out of range");
|
MAP.put(FLOAT_LITERAL_OUT_OF_RANGE, "The value is out of range");
|
||||||
MAP.put(INCORRECT_CHARACTER_LITERAL, "Incorrect character literal");
|
MAP.put(INCORRECT_CHARACTER_LITERAL, "Incorrect character literal");
|
||||||
MAP.put(EMPTY_CHARACTER_LITERAL, "Empty character literal");
|
MAP.put(EMPTY_CHARACTER_LITERAL, "Empty character literal");
|
||||||
|
MAP.put(ILLEGAL_UNDERSCORE, "Illegal underscore");
|
||||||
MAP.put(TOO_MANY_CHARACTERS_IN_CHARACTER_LITERAL, "Too many characters in a character literal ''{0}''", ELEMENT_TEXT);
|
MAP.put(TOO_MANY_CHARACTERS_IN_CHARACTER_LITERAL, "Too many characters in a character literal ''{0}''", ELEMENT_TEXT);
|
||||||
MAP.put(ILLEGAL_ESCAPE, "Illegal escape: ''{0}''", ELEMENT_TEXT);
|
MAP.put(ILLEGAL_ESCAPE, "Illegal escape: ''{0}''", ELEMENT_TEXT);
|
||||||
MAP.put(NULL_FOR_NONNULL_TYPE, "Null can not be a value of a non-null type {0}", RENDER_TYPE);
|
MAP.put(NULL_FOR_NONNULL_TYPE, "Null can not be a value of a non-null type {0}", RENDER_TYPE);
|
||||||
|
|||||||
+14
-3
@@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.constants.evaluate
|
package org.jetbrains.kotlin.resolve.constants.evaluate
|
||||||
|
|
||||||
|
import com.intellij.psi.tree.IElementType
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.util.text.LiteralFormatUtil
|
||||||
|
import org.jetbrains.kotlin.KtNodeType
|
||||||
import org.jetbrains.kotlin.KtNodeTypes
|
import org.jetbrains.kotlin.KtNodeTypes
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
@@ -283,8 +286,7 @@ private class ConstantExpressionEvaluatorVisitor(
|
|||||||
if (nodeElementType == KtNodeTypes.NULL) return factory.createNullValue().wrap()
|
if (nodeElementType == KtNodeTypes.NULL) return factory.createNullValue().wrap()
|
||||||
|
|
||||||
val result: Any? = when (nodeElementType) {
|
val result: Any? = when (nodeElementType) {
|
||||||
KtNodeTypes.INTEGER_CONSTANT -> parseLong(text)
|
KtNodeTypes.INTEGER_CONSTANT, KtNodeTypes.FLOAT_CONSTANT -> parseNumericLiteral(text, nodeElementType)
|
||||||
KtNodeTypes.FLOAT_CONSTANT -> parseFloatingLiteral(text)
|
|
||||||
KtNodeTypes.BOOLEAN_CONSTANT -> parseBoolean(text)
|
KtNodeTypes.BOOLEAN_CONSTANT -> parseBoolean(text)
|
||||||
KtNodeTypes.CHARACTER_CONSTANT -> CompileTimeConstantChecker.parseChar(expression)
|
KtNodeTypes.CHARACTER_CONSTANT -> CompileTimeConstantChecker.parseChar(expression)
|
||||||
else -> throw IllegalArgumentException("Unsupported constant: " + expression)
|
else -> throw IllegalArgumentException("Unsupported constant: " + expression)
|
||||||
@@ -765,7 +767,16 @@ private class ConstantExpressionEvaluatorVisitor(
|
|||||||
|
|
||||||
private fun hasLongSuffix(text: String) = text.endsWith('l') || text.endsWith('L')
|
private fun hasLongSuffix(text: String) = text.endsWith('l') || text.endsWith('L')
|
||||||
|
|
||||||
fun parseLong(text: String): Long? {
|
private fun parseNumericLiteral(text: String, type: IElementType): Any? {
|
||||||
|
val canonicalText = LiteralFormatUtil.removeUnderscores(text)
|
||||||
|
return when (type) {
|
||||||
|
KtNodeTypes.INTEGER_CONSTANT -> parseLong(canonicalText)
|
||||||
|
KtNodeTypes.FLOAT_CONSTANT -> parseFloatingLiteral(canonicalText)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLong(text: String): Long? {
|
||||||
try {
|
try {
|
||||||
fun substringLongSuffix(s: String) = if (hasLongSuffix(text)) s.substring(0, s.length - 1) else s
|
fun substringLongSuffix(s: String) = if (hasLongSuffix(text)) s.substring(0, s.length - 1) else s
|
||||||
fun parseLong(text: String, radix: Int) = java.lang.Long.parseLong(substringLongSuffix(text), radix)
|
fun parseLong(text: String, radix: Int) = java.lang.Long.parseLong(substringLongSuffix(text), radix)
|
||||||
|
|||||||
+52
@@ -17,6 +17,7 @@
|
|||||||
package org.jetbrains.kotlin.types.expressions;
|
package org.jetbrains.kotlin.types.expressions;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.intellij.openapi.util.text.StringUtil;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.tree.IElementType;
|
import com.intellij.psi.tree.IElementType;
|
||||||
import com.intellij.psi.tree.TokenSet;
|
import com.intellij.psi.tree.TokenSet;
|
||||||
@@ -27,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.KtNodeTypes;
|
import org.jetbrains.kotlin.KtNodeTypes;
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||||
|
import org.jetbrains.kotlin.config.LanguageFeature;
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors;
|
import org.jetbrains.kotlin.diagnostics.Errors;
|
||||||
@@ -71,9 +73,12 @@ import org.jetbrains.kotlin.types.expressions.unqualifiedSuper.UnqualifiedSuperK
|
|||||||
import org.jetbrains.kotlin.util.OperatorNameConventions;
|
import org.jetbrains.kotlin.util.OperatorNameConventions;
|
||||||
import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
|
import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isExtensionFunctionType;
|
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isExtensionFunctionType;
|
||||||
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionType;
|
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionType;
|
||||||
@@ -185,6 +190,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
checkLiteralPrefixAndSuffix(expression, context);
|
checkLiteralPrefixAndSuffix(expression, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (elementType == KtNodeTypes.INTEGER_CONSTANT || elementType == KtNodeTypes.FLOAT_CONSTANT) {
|
||||||
|
checkUnderscores(expression, elementType, context);
|
||||||
|
}
|
||||||
|
|
||||||
CompileTimeConstant<?> compileTimeConstant = components.constantExpressionEvaluator.evaluateExpression(
|
CompileTimeConstant<?> compileTimeConstant = components.constantExpressionEvaluator.evaluateExpression(
|
||||||
expression, context.trace, context.expectedType
|
expression, context.trace, context.expectedType
|
||||||
);
|
);
|
||||||
@@ -206,6 +215,49 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
return components.dataFlowAnalyzer.createCompileTimeConstantTypeInfo(compileTimeConstant, expression, context);
|
return components.dataFlowAnalyzer.createCompileTimeConstantTypeInfo(compileTimeConstant, expression, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Pattern FP_LITERAL_PARTS = Pattern.compile("(?:([_\\d]*)\\.?([_\\d]*)e?[+-]?([_\\d]*))[f]?");
|
||||||
|
|
||||||
|
private void checkUnderscores(
|
||||||
|
@NotNull KtConstantExpression expression,
|
||||||
|
@NotNull IElementType elementType,
|
||||||
|
@NotNull ExpressionTypingContext context
|
||||||
|
) {
|
||||||
|
String text = expression.getText().toLowerCase();
|
||||||
|
|
||||||
|
if (!text.contains("_")) return;
|
||||||
|
|
||||||
|
if (!components.languageVersionSettings.supportsFeature(LanguageFeature.UnderscoresInNumericLiterals)) {
|
||||||
|
context.trace.report(Errors.UNSUPPORTED_FEATURE.on(expression, LanguageFeature.UnderscoresInNumericLiterals));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> parts;
|
||||||
|
|
||||||
|
if (elementType == KtNodeTypes.INTEGER_CONSTANT) {
|
||||||
|
int start = 0;
|
||||||
|
int end = expression.getText().length();
|
||||||
|
if (text.startsWith("0x") || text.startsWith("0b")) start += 2;
|
||||||
|
if (StringUtil.endsWithChar(text, 'l')) --end;
|
||||||
|
parts = Collections.singletonList(text.substring(start, end));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Matcher matcher = FP_LITERAL_PARTS.matcher(text);
|
||||||
|
parts = new ArrayList<String>();
|
||||||
|
if (matcher.matches()) {
|
||||||
|
for (int i = 0; i < matcher.groupCount(); i++) {
|
||||||
|
parts.add(matcher.group(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String part : parts) {
|
||||||
|
if (part != null && (part.startsWith("_") || part.endsWith("_"))) {
|
||||||
|
context.trace.report(Errors.ILLEGAL_UNDERSCORE.on(expression));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public KotlinType getDefaultType(IElementType constantType) {
|
public KotlinType getDefaultType(IElementType constantType) {
|
||||||
KotlinBuiltIns builtIns = components.builtIns;
|
KotlinBuiltIns builtIns = components.builtIns;
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_EXPRESSION
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1_<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>0x_f<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>0X_f<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>0b_1<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>0B_1<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1.0_<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1_.1<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1._1<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1_._1<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1.0_e1<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1.0E_1<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>0Xe_<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1.1_e-1_23<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1.0e+_0<!>
|
||||||
|
<!ILLEGAL_UNDERSCORE!>1.0e-_0<!>
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun foo(): kotlin.Unit
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// !LANGUAGE: -UnderscoresInNumericLiterals
|
||||||
|
// !DIAGNOSTICS: -UNUSED_EXPRESSION
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
<!UNSUPPORTED_FEATURE!>100_1<!>
|
||||||
|
<!UNSUPPORTED_FEATURE!>3_.1<!>
|
||||||
|
<!UNSUPPORTED_FEATURE!>3_._1<!>
|
||||||
|
<!UNSUPPORTED_FEATURE!>2___4<!>
|
||||||
|
<!UNSUPPORTED_FEATURE!>123_<!>
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun foo(): kotlin.Unit
|
||||||
@@ -21118,6 +21118,27 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/diagnostics/tests/underscoresInNumericLiterals")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class UnderscoresInNumericLiterals extends AbstractDiagnosticsTest {
|
||||||
|
public void testAllFilesPresentInUnderscoresInNumericLiterals() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/underscoresInNumericLiterals"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("illegalUnderscores.kt")
|
||||||
|
public void testIllegalUnderscores() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/underscoresInNumericLiterals/illegalUnderscores.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noUnderscores.kt")
|
||||||
|
public void testNoUnderscores() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/underscoresInNumericLiterals/noUnderscores.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/diagnostics/tests/unit")
|
@TestMetadata("compiler/testData/diagnostics/tests/unit")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ enum class LanguageFeature(val sinceVersion: LanguageVersion) {
|
|||||||
InlineProperties(KOTLIN_1_1),
|
InlineProperties(KOTLIN_1_1),
|
||||||
DestructuringLambdaParameters(KOTLIN_1_1),
|
DestructuringLambdaParameters(KOTLIN_1_1),
|
||||||
SingleUnderscoreForParameterName(KOTLIN_1_1),
|
SingleUnderscoreForParameterName(KOTLIN_1_1),
|
||||||
DslMarkersSupport(KOTLIN_1_1)
|
DslMarkersSupport(KOTLIN_1_1),
|
||||||
|
UnderscoresInNumericLiterals(KOTLIN_1_1)
|
||||||
;
|
;
|
||||||
|
|
||||||
val presentableText: String
|
val presentableText: String
|
||||||
|
|||||||
Reference in New Issue
Block a user