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
}