Add support of internal arguments for language feature settings

Arguments are passed in form '-XXLanguage:+LanguageFeatureName' for enabling
LanguageFeature.LanguageFeatureName, and '-XXLanguage:-LanguageFeatureName'
for disabling.

Note that they do override other settings, including 'language-version'
or extra ('-X') args.
This commit is contained in:
Dmitry Savvinov
2018-03-27 13:41:03 +03:00
parent ae119a09eb
commit c33b7377da
24 changed files with 346 additions and 23 deletions
@@ -230,8 +230,34 @@ abstract class CommonCompilerArguments : CommonToolArguments() {
if (!contains(it)) put(it, LanguageFeature.State.ENABLED)
}
}
// Internal arguments should go last, because it may be useful to override
// some feature state via -XX (even if some -X flags were passed)
if (internalArguments.isNotEmpty()) {
configureLanguageFeaturesFromInternalArgs(collector)
}
}
private fun HashMap<LanguageFeature, LanguageFeature.State>.configureLanguageFeaturesFromInternalArgs(collector: MessageCollector) {
val languageSettingsParser = LanguageSettingsParser()
val featuresThatForcePreReleaseBinaries = mutableListOf<LanguageFeature>()
for (argument in internalArguments) {
val (feature, state) = languageSettingsParser.parseInternalArgument(argument, collector) ?: continue
put(feature, state)
if (state == LanguageFeature.State.ENABLED && feature.forcesPreReleaseBinariesIfEnabled()) {
featuresThatForcePreReleaseBinaries += feature
}
}
if (featuresThatForcePreReleaseBinaries.isNotEmpty()) {
collector.report(
CompilerMessageSeverity.STRONG_WARNING,
"Following manually enabled features will force generation of pre-release binaries: ${featuresThatForcePreReleaseBinaries.joinToString()}"
)
}
}
fun configureLanguageVersionSettings(collector: MessageCollector): LanguageVersionSettings {
// If only "-api-version" is specified, language version is assumed to be the latest stable
@@ -49,4 +49,6 @@ abstract class CommonToolArguments : Freezable(), Serializable {
@GradleOption(DefaultValues.BooleanFalseDefault::class)
@Argument(value = "-Werror", description = "Report an error if there are any warnings")
var allWarningsAsErrors: Boolean by FreezableVar(false)
var internalArguments: List<String> by FreezableVar(emptyList())
}
@@ -0,0 +1,87 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.common.arguments
import org.jetbrains.kotlin.cli.common.arguments.InternalArgumentParser.Companion.INTERNAL_ARGUMENT_PREFIX
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.LanguageFeature
/**
* Arguments that can drastically change compiler behavior,
* breaking stability/compatibility.
*
* Internal arguments are split into 'families', each family
* with its own set of arguments, settings and parsing rules
*
* Internal arguments start with '-XX' prefix, followed by
* family name. Everything after that is handled by the corresponding
* parser of that particular family.
*/
interface InternalArgumentParser<A : InternalArgument> {
// Should be fast
fun canParse(arg: String): Boolean
fun parseInternalArgument(arg: String, messageCollector: MessageCollector): A?
companion object {
internal const val INTERNAL_ARGUMENT_PREFIX = "-XX"
internal val PARSERS: List<InternalArgumentParser<*>> = listOf(
LanguageSettingsParser()
)
}
}
abstract class AbstractInternalArgumentParser<A : InternalArgument>(familyName: String) : InternalArgumentParser<A> {
private val wholePrefix: String = INTERNAL_ARGUMENT_PREFIX + familyName
override fun canParse(arg: String): Boolean = arg.startsWith(wholePrefix)
override fun parseInternalArgument(arg: String, messageCollector: MessageCollector): A? {
if (!arg.startsWith(wholePrefix)) return null
return parseTail(arg.removePrefix(wholePrefix), arg, messageCollector)
}
abstract fun parseTail(tail: String, wholeArgument: String, messageCollector: MessageCollector): A?
}
// Arguments of form '-XXLanguage:+LanguageFeature' or '-XXLanguage:-LanguageFeature', which enable or disable corresponding LanguageFeature.
class LanguageSettingsParser : AbstractInternalArgumentParser<ManualLanguageFeatureSetting>("Language") {
// Expected tail form: ':(+|-)<language feature name>'
override fun parseTail(tail: String, wholeArgument: String, messageCollector: MessageCollector): ManualLanguageFeatureSetting? {
fun reportAndReturnNull(message: String): Nothing? {
messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, message)
return null
}
val colon = tail.getOrNull(0) ?: return reportAndReturnNull("Incorrect internal argument syntax, missing colon: $wholeArgument")
val modificator = tail.getOrNull(1)
val languageFeatureState = when (modificator) {
'+' -> LanguageFeature.State.ENABLED
'-' -> LanguageFeature.State.DISABLED
else -> return reportAndReturnNull("Incorrect internal argument syntax, missing modificator: $wholeArgument")
}
val languageFeatureName = tail.substring(2)
if (languageFeatureName.isEmpty()) return reportAndReturnNull("Empty language feature name for internal argument '$wholeArgument'")
val languageFeature = LanguageFeature.fromString(languageFeatureName)
?: return reportAndReturnNull("Unknown language feature '$languageFeatureName' in passed internal argument '$wholeArgument'")
return ManualLanguageFeatureSetting(languageFeature, languageFeatureState)
}
}
interface InternalArgument
data class ManualLanguageFeatureSetting(val languageFeature: LanguageFeature, val state: LanguageFeature.State) : InternalArgument
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.cli.common.arguments
import org.jetbrains.kotlin.utils.SmartList
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
@Target(AnnotationTarget.PROPERTY)
@@ -102,6 +101,7 @@ fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, resu
}
val freeArgs = ArrayList<String>()
val internalArguments = ArrayList<String>()
var i = 0
loop@ while (i < args.size) {
@@ -116,6 +116,21 @@ fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, resu
continue
}
if (arg.startsWith(InternalArgumentParser.INTERNAL_ARGUMENT_PREFIX)) {
val matchingParsers = InternalArgumentParser.PARSERS.filter { it.canParse(arg) }
assert(matchingParsers.size <= 1) { "Internal error: internal argument $arg can be ambiguously parsed by parsers ${matchingParsers.joinToString()}" }
val parser = matchingParsers.firstOrNull()
if (parser == null) {
errors.unknownExtraFlags += arg
} else {
internalArguments.add(arg)
}
continue
}
val argumentField = properties.firstOrNull { it.matches(arg) }
if (argumentField == null) {
when {
@@ -154,6 +169,7 @@ fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, resu
}
result.freeArgs += freeArgs
result.internalArguments += internalArguments
}
private fun <A : CommonToolArguments> updateField(property: KMutableProperty1<A, Any?>, result: A, value: Any, delimiter: String) {
@@ -38,10 +38,10 @@ abstract class CLITool<A : CommonToolArguments> {
}
protected fun exec(
errStream: PrintStream,
services: Services,
messageRenderer: MessageRenderer,
args: Array<out String>
errStream: PrintStream,
services: Services,
messageRenderer: MessageRenderer,
args: Array<out String>
): ExitCode {
val arguments = createArguments()
parseCommandLineArguments(args.asList(), arguments)
@@ -67,8 +67,7 @@ abstract class CLITool<A : CommonToolArguments> {
}
return exec(collector, services, arguments)
}
finally {
} finally {
errStream.print(messageRenderer.renderConclusion())
if (PlainTextMessageRenderer.COLOR_ENABLED) {
@@ -84,12 +83,11 @@ abstract class CLITool<A : CommonToolArguments> {
val fixedMessageCollector = if (arguments.suppressWarnings && !arguments.allWarningsAsErrors) {
FilteringMessageCollector(messageCollector, Predicate.isEqual(CompilerMessageSeverity.WARNING))
}
else {
} else {
messageCollector
}
reportArgumentParseProblems(fixedMessageCollector, arguments.errors)
reportArgumentParseProblems(fixedMessageCollector, arguments)
return execImpl(fixedMessageCollector, services, arguments)
}
@@ -117,13 +115,16 @@ abstract class CLITool<A : CommonToolArguments> {
}
}
private fun reportArgumentParseProblems(collector: MessageCollector, errors: ArgumentParseErrors) {
private fun reportArgumentParseProblems(collector: MessageCollector, arguments: A) {
val errors = arguments.errors
for (flag in errors.unknownExtraFlags) {
collector.report(STRONG_WARNING, "Flag is not supported by this version of the compiler: $flag")
}
for (argument in errors.extraArgumentsPassedInObsoleteForm) {
collector.report(STRONG_WARNING, "Advanced option value is passed in an obsolete form. Please use the '=' character " +
"to specify the value: $argument=...")
collector.report(
STRONG_WARNING, "Advanced option value is passed in an obsolete form. Please use the '=' character " +
"to specify the value: $argument=..."
)
}
for ((key, value) in errors.duplicateArguments) {
collector.report(STRONG_WARNING, "Argument $key is passed multiple times. Only the last value will be used: $value")
@@ -131,6 +132,17 @@ abstract class CLITool<A : CommonToolArguments> {
for ((deprecatedName, newName) in errors.deprecatedArguments) {
collector.report(STRONG_WARNING, "Argument $deprecatedName is deprecated. Please use $newName instead")
}
if (arguments.internalArguments.isNotEmpty()) {
collector.report(
STRONG_WARNING,
"ATTENTION!\n" +
"This build uses internal compiler arguments:\n" +
arguments.internalArguments.joinToString(prefix = "\n", postfix = "\n\n", separator = "\n") +
"This mode is strictly prohibited for production use,\n" +
"as no stability/compatibility guarantees are given on\n" +
"compiler or generated code. Use it at your own risk!\n"
)
}
}
private fun <A : CommonToolArguments> printVersionIfNeeded(messageCollector: MessageCollector, arguments: A) {
@@ -166,8 +178,7 @@ abstract class CLITool<A : CommonToolArguments> {
fun doMainNoExit(compiler: CLITool<*>, args: Array<String>): ExitCode {
try {
return compiler.exec(System.err, *args)
}
catch (e: CompileEnvironmentException) {
} catch (e: CompileEnvironmentException) {
System.err.println(e.message)
return ExitCode.INTERNAL_ERROR
}
+1
View File
@@ -0,0 +1 @@
inline class Foo(val x: Int)
@@ -0,0 +1,6 @@
-language-version
1.3
-XXLanguage\:-InlineClasses
$TESTDATA_DIR$/inlineClass.kt
-d
$TEMP_DIR$
@@ -0,0 +1,14 @@
warning: ATTENTION!
This build uses internal compiler arguments:
-XXLanguage:-InlineClasses
This mode is strictly prohibited for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!
warning: language version 1.3 is experimental, there are no backwards compatibility guarantees for new language and library features
compiler/testData/cli/jvm/inlineClass.kt:1:1: error: the feature "inline classes" is experimental and should be enabled explicitly
inline class Foo(val x: Int)
^
COMPILATION_ERROR
@@ -0,0 +1,4 @@
$TESTDATA_DIR$/inlineClass.kt
-d
$TEMP_DIR$
-XXLanguage\:+
@@ -0,0 +1,14 @@
warning: ATTENTION!
This build uses internal compiler arguments:
-XXLanguage:+
This mode is strictly prohibited for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!
warning: empty language feature name for internal argument '-XXLanguage:+'
compiler/testData/cli/jvm/inlineClass.kt:1:1: error: the feature "inline classes" is experimental and should be enabled explicitly
inline class Foo(val x: Int)
^
COMPILATION_ERROR
@@ -0,0 +1,6 @@
$TESTDATA_DIR$/inlineClass.kt
-d
$TEMP_DIR$
-language-version
1.2
-XXLanguage\:+InlineClasses
@@ -0,0 +1,11 @@
warning: ATTENTION!
This build uses internal compiler arguments:
-XXLanguage:+InlineClasses
This mode is strictly prohibited for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!
warning: following manually enabled features will force generation of pre-release binaries: InlineClasses
OK
@@ -0,0 +1,4 @@
$TESTDATA_DIR$/inlineClass.kt
-d
$TEMP_DIR$
-XXLanguage\:InlineClasses
@@ -0,0 +1,14 @@
warning: ATTENTION!
This build uses internal compiler arguments:
-XXLanguage:InlineClasses
This mode is strictly prohibited for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!
warning: incorrect internal argument syntax, missing modificator: -XXLanguage:InlineClasses
compiler/testData/cli/jvm/inlineClass.kt:1:1: error: the feature "inline classes" is experimental and should be enabled explicitly
inline class Foo(val x: Int)
^
COMPILATION_ERROR
@@ -0,0 +1,4 @@
$TESTDATA_DIR$/inlineClass.kt
-d
$TEMP_DIR$
-XXLanguage\:+UnknownFeature
@@ -0,0 +1,14 @@
warning: ATTENTION!
This build uses internal compiler arguments:
-XXLanguage:+UnknownFeature
This mode is strictly prohibited for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!
warning: unknown language feature 'UnknownFeature' in passed internal argument '-XXLanguage:+UnknownFeature'
compiler/testData/cli/jvm/inlineClass.kt:1:1: error: the feature "inline classes" is experimental and should be enabled explicitly
inline class Foo(val x: Int)
^
COMPILATION_ERROR
+4
View File
@@ -0,0 +1,4 @@
$TESTDATA_DIR$/inlineClass.kt
-d
$TEMP_DIR$
-XX:+InlineClasses
+5
View File
@@ -0,0 +1,5 @@
warning: flag is not supported by this version of the compiler: -XX:+InlineClasses
compiler/testData/cli/jvm/inlineClass.kt:1:1: error: the feature "inline classes" is experimental and should be enabled explicitly
inline class Foo(val x: Int)
^
COMPILATION_ERROR
@@ -0,0 +1,5 @@
$TESTDATA_DIR$/legacySmartCastsAfterTry.kt
-d
$TEMP_DIR$
-XXLanguage\:+SoundSmartCastsAfterTry
-Xlegacy-smart-cast-after-try
@@ -0,0 +1,13 @@
warning: ATTENTION!
This build uses internal compiler arguments:
-XXLanguage:+SoundSmartCastsAfterTry
This mode is strictly prohibited for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!
compiler/testData/cli/jvm/legacySmartCastsAfterTry.kt:8:9: error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
some.length
^
COMPILATION_ERROR
@@ -42,6 +42,8 @@ data class CompilerTestLanguageVersionSettings(
override fun getFeatureSupport(feature: LanguageFeature): LanguageFeature.State =
languageFeatures[feature] ?: delegate.getFeatureSupport(feature)
override fun isPreRelease(): Boolean = languageVersion.isPreRelease()
@Suppress("UNCHECKED_CAST")
override fun <T> getFlag(flag: AnalysisFlag<T>): T = analysisFlags[flag] as T? ?: flag.defaultValue
}
@@ -232,6 +232,48 @@ public class CliTestGenerated extends AbstractCliTest {
runTest("compiler/testData/cli/jvm/inlineCycle.args");
}
@TestMetadata("internalArgDisableLanguageFeature.args")
public void testInternalArgDisableLanguageFeature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/internalArgDisableLanguageFeature.args");
doJvmTest(fileName);
}
@TestMetadata("internalArgEmptyFeatureName.args")
public void testInternalArgEmptyFeatureName() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/internalArgEmptyFeatureName.args");
doJvmTest(fileName);
}
@TestMetadata("internalArgEnableLanguageFeature.args")
public void testInternalArgEnableLanguageFeature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/internalArgEnableLanguageFeature.args");
doJvmTest(fileName);
}
@TestMetadata("internalArgMissingModificator.args")
public void testInternalArgMissingModificator() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/internalArgMissingModificator.args");
doJvmTest(fileName);
}
@TestMetadata("internalArgUnrecognizedFeature.args")
public void testInternalArgUnrecognizedFeature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/internalArgUnrecognizedFeature.args");
doJvmTest(fileName);
}
@TestMetadata("internalArgWrongPrefix.args")
public void testInternalArgWrongPrefix() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/internalArgWrongPrefix.args");
doJvmTest(fileName);
}
@TestMetadata("internalArgumentOverrideExtraArgument.args")
public void testInternalArgumentOverrideExtraArgument() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/internalArgumentOverrideExtraArgument.args");
doJvmTest(fileName);
}
@TestMetadata("javaSrcWrongPackage.args")
public void testJavaSrcWrongPackage() throws Exception {
runTest("compiler/testData/cli/jvm/javaSrcWrongPackage.args");
@@ -14,6 +14,7 @@ enum class LanguageFeature(
val sinceApiVersion: ApiVersion = ApiVersion.KOTLIN_1_0,
val hintUrl: String? = null,
val defaultState: State = State.ENABLED,
val forcesPreReleaseBinaries: Boolean = false,
val enabledInProgressiveMode: Boolean = false
) {
// Note: names of these entries are also used in diagnostic tests and in user-visible messages (see presentableText below)
@@ -65,14 +66,14 @@ enum class LanguageFeature(
ProperVisibilityForCompanionObjectInstanceField(KOTLIN_1_3, enabledInProgressiveMode = true),
ProperForInArrayLoopRangeVariableAssignmentSemantic(KOTLIN_1_3, enabledInProgressiveMode = true),
NestedClassesInAnnotations(KOTLIN_1_3),
JvmStaticInInterface(KOTLIN_1_3),
JvmStaticInInterface(KOTLIN_1_3, forcesPreReleaseBinaries = true),
ProhibitVisibilityOfNestedClassifiersFromSupertypesOfCompanion(KOTLIN_1_3, enabledInProgressiveMode = true),
ProhibitNonConstValuesAsVarargsInAnnotations(KOTLIN_1_3, enabledInProgressiveMode = true),
ReleaseCoroutines(KOTLIN_1_3),
ReleaseCoroutines(KOTLIN_1_3, forcesPreReleaseBinaries = true),
ReadDeserializedContracts(KOTLIN_1_3),
UseReturnsEffect(KOTLIN_1_3),
UseCallsInPlaceEffect(KOTLIN_1_3),
AllowContractsForCustomFunctions(KOTLIN_1_3),
AllowContractsForCustomFunctions(KOTLIN_1_3, forcesPreReleaseBinaries = true),
ProhibitLocalAnnotations(KOTLIN_1_3, enabledInProgressiveMode = true),
StrictJavaNullabilityAssertions(sinceVersion = null, defaultState = State.DISABLED),
@@ -90,7 +91,7 @@ enum class LanguageFeature(
NewInference(sinceVersion = KOTLIN_1_3, defaultState = State.DISABLED),
InlineClasses(sinceVersion = null, defaultState = State.DISABLED),
InlineClasses(sinceVersion = null, defaultState = State.DISABLED, forcesPreReleaseBinaries = true),
;
@@ -147,7 +148,12 @@ interface LanguageVersionSettings {
fun getFeatureSupport(feature: LanguageFeature): LanguageFeature.State
fun supportsFeature(feature: LanguageFeature): Boolean =
getFeatureSupport(feature).let { it == LanguageFeature.State.ENABLED || it == LanguageFeature.State.ENABLED_WITH_WARNING }
getFeatureSupport(feature).let {
it == LanguageFeature.State.ENABLED ||
it == LanguageFeature.State.ENABLED_WITH_WARNING
}
fun isPreRelease(): Boolean
fun <T> getFlag(flag: AnalysisFlag<T>): T
@@ -192,17 +198,24 @@ class LanguageVersionSettingsImpl @JvmOverloads constructor(
}
}
override fun isPreRelease(): Boolean = languageVersion.isPreRelease() ||
specificFeatures.any { (feature, state) ->
state == LanguageFeature.State.ENABLED && feature.forcesPreReleaseBinariesIfEnabled()
}
companion object {
@JvmField
val DEFAULT = LanguageVersionSettingsImpl(LanguageVersion.LATEST_STABLE, ApiVersion.LATEST_STABLE)
}
}
fun LanguageVersionSettings.isPreRelease(): Boolean =
languageVersion.isPreRelease()
fun LanguageVersion.isPreRelease(): Boolean {
if (!isStable) return true
return KotlinCompilerVersion.isPreRelease() && this == LanguageVersion.LATEST_STABLE
}
fun LanguageFeature.forcesPreReleaseBinariesIfEnabled(): Boolean {
val isFeatureNotReleasedYet = sinceVersion?.isStable != true
return isFeatureNotReleasedYet && forcesPreReleaseBinaries
}
@@ -25,6 +25,11 @@ public class ReleaseCoroutinesDisabledLanguageVersionSettings implements Languag
return delegate.getFeatureSupport(feature);
}
@Override
public boolean isPreRelease() {
return delegate.isPreRelease();
}
@Override
public <T> T getFlag(@NotNull AnalysisFlag<? extends T> flag) {
return delegate.getFlag(flag);