blob: 69ba2802955caf003730c3d59da665986992fb86 [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library front_end.compiler_options;
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
show DiagnosticMessage, DiagnosticMessageHandler;
import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
import 'package:kernel/ast.dart' show Version;
import 'package:kernel/default_language_version.dart' as kernel
show defaultLanguageVersion;
import 'package:kernel/target/targets.dart' show Target;
import '../base/nnbd_mode.dart';
import 'experimental_flags.dart'
show
AllowedExperimentalFlags,
defaultExperimentalFlags,
ExperimentalFlag,
expiredExperimentalFlags,
parseExperimentalFlag;
import 'experimental_flags.dart' as flags
show
getExperimentEnabledVersionInLibrary,
isExperimentEnabled,
isExperimentEnabledInLibrary,
isExperimentEnabledInLibraryByVersion;
import 'file_system.dart' show FileSystem;
import 'standard_file_system.dart' show StandardFileSystem;
import '../api_unstable/util.dart';
export 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
show DiagnosticMessage;
/// Front-end options relevant to compiler back ends.
///
/// Not intended to be implemented or extended by clients.
class CompilerOptions {
/// The URI of the root of the Dart SDK (typically a "file:" URI).
///
/// If `null`, the SDK will be searched for using
/// [Platform.resolvedExecutable] as a starting point.
Uri? sdkRoot;
/// Uri to a platform libraries specification file.
///
/// A libraries specification file is a JSON file that describes how to map
/// `dart:*` libraries to URIs in the underlying [fileSystem]. See
/// `package:front_end/src/base/libraries_specification.dart` for details on
/// the format.
///
/// If a value is not specified and `compileSdk = true`, the compiler will
/// infer at a default location under [sdkRoot], typically under
/// `lib/libraries.json`.
Uri? librariesSpecificationUri;
DiagnosticMessageHandler? onDiagnostic;
/// URI of the ".dart_tool/package_config.json" or ".packages" file
/// (typically a "file:" URI).
///
/// If a ".packages" file is given and a ".dart_tool/package_config.json" file
/// exists next to it, the ".dart_tool/package_config.json" file is used
/// instead.
///
/// If `null`, the file will be found via the standard package_config search
/// algorithm.
///
/// If the URI's path component is empty (e.g. `new Uri()`), no packages file
/// will be used.
Uri? packagesFileUri;
/// URIs of additional dill files.
///
/// These will be loaded and linked into the output.
///
/// The components provided here should be closed: any libraries that they
/// reference should be defined in a component in [additionalDills] or
/// [sdkSummary].
List<Uri> additionalDills = [];
/// URI of the SDK summary file (typically a "file:" URI).
///
/// This should should be a summary previously generated by this package (and
/// not the similarly named summary files from `package:analyzer`.)
///
/// If `null` and [compileSdk] is false, the SDK summary will be searched for
/// at a default location within [sdkRoot].
Uri? sdkSummary;
/// The declared variables for use by configurable imports and constant
/// evaluation.
Map<String, String>? declaredVariables;
/// The [FileSystem] which should be used by the front end to access files.
///
/// All file system access performed by the front end goes through this
/// mechanism, with one exception: if no value is specified for
/// [packagesFileUri], the packages file is located using the actual physical
/// file system. TODO(paulberry): fix this.
FileSystem fileSystem = StandardFileSystem.instance;
/// Whether to generate code for the SDK.
///
/// By default the front end resolves components using a prebuilt SDK summary.
/// When this option is `true`, [sdkSummary] must be null.
bool compileSdk = false;
/// Enable or disable experimental features. Features mapping to `true` are
/// explicitly enabled. Features mapping to `false` are explicitly disabled.
/// Features not mentioned in the map will have their default value.
Map<ExperimentalFlag, bool> explicitExperimentalFlags =
<ExperimentalFlag, bool>{};
Map<ExperimentalFlag, bool>? defaultExperimentFlagsForTesting;
AllowedExperimentalFlags? allowedExperimentalFlagsForTesting;
Map<ExperimentalFlag, Version>? experimentEnabledVersionForTesting;
Map<ExperimentalFlag, Version>? experimentReleasedVersionForTesting;
/// Environment map used when evaluating `bool.fromEnvironment`,
/// `int.fromEnvironment` and `String.fromEnvironment` during constant
/// evaluation. If the map is `null`, all environment constants will be left
/// unevaluated and can be evaluated by a constant evaluator later.
Map<String, String>? environmentDefines = null;
/// Report an error if a constant could not be evaluated (either because it
/// is an environment constant and no environment was specified, or because
/// it refers to a constructor or variable initializer that is not available).
bool errorOnUnevaluatedConstant = false;
/// The target platform that will consume the compiled code.
///
/// Used to provide platform-specific details to the compiler like:
/// * the set of libraries are part of a platform's SDK (e.g. dart:html for
/// dart2js, dart:ui for flutter).
///
/// * what kernel transformations should be applied to the component
/// (async/await, mixin inlining, etc).
///
/// * how to deal with non-standard features like `native` extensions.
///
/// If not specified, the default target is the VM.
Target? target;
/// Whether to show verbose messages (mainly for debugging and performance
/// tracking).
///
/// Messages are printed on stdout.
// TODO(sigmund): improve the diagnostics API to provide mechanism to
// intercept verbose data (Issue #30056)
bool verbose = false;
/// Whether to run extra verification steps to validate that compiled
/// components are well formed.
///
/// Errors are reported via the [onDiagnostic] callback.
bool verify = false;
/// Whether to - if verifying - skip the platform.
bool skipPlatformVerification = false;
/// Whether to dump generated components in a text format (also mainly for
/// debugging).
///
/// Dumped data is printed in stdout.
bool debugDump = false;
/// Whether to omit the platform when serializing the result from a `fasta
/// compile` run.
bool omitPlatform = false;
/// Whether to set the exit code to non-zero if any problem (including
/// warning, etc.) is encountered during compilation.
bool setExitCodeOnProblem = false;
/// Whether to embed the input sources in generated kernel components.
///
/// The kernel `Component` API includes a `uriToSource` map field that is used
/// to embed the entire contents of the source files. This part of the kernel
/// API is in flux and it is not necessary for some tools. Today it is used
/// for translating error locations and stack traces in the VM.
// TODO(sigmund): change the default.
bool embedSourceText = true;
/// Whether the compiler should throw as soon as it encounters a
/// compilation error.
///
/// Typically used by developers to debug internals of the compiler.
bool throwOnErrorsForDebugging = false;
/// Whether the compiler should throw as soon as it encounters a
/// compilation warning.
///
/// Typically used by developers to debug internals of the compiler.
bool throwOnWarningsForDebugging = false;
/// For the [throwOnErrorsForDebugging] or [throwOnWarningsForDebugging]
/// options, skip this number of otherwise fatal diagnostics without throwing.
/// I.e. the default value of 0 means throw on the first fatal diagnostic.
///
/// If the value is negative, print a stack trace for every fatal
/// diagnostic, but do not stop the compilation.
int skipForDebugging = 0;
/// Whether to write a file (e.g. a dill file) when reporting a crash.
bool writeFileOnCrashReport = true;
/// Whether nnbd weak, strong or agnostic mode is used if experiment
/// 'non-nullable' is enabled.
NnbdMode nnbdMode = NnbdMode.Weak;
/// Whether to emit a warning when a ReachabilityError is thrown to ensure
/// soundness in mixed mode.
bool warnOnReachabilityCheck = false;
/// The current sdk version string, e.g. "2.6.0-edge.sha1hash".
/// For instance used for language versioning (specifying the maximum
/// version).
String currentSdkVersion = "${kernel.defaultLanguageVersion.major}"
"."
"${kernel.defaultLanguageVersion.minor}";
/// If `true`, a '.d' file with input dependencies is generated when
/// compiling the platform dill.
bool emitDeps = true;
/// Set of invocation modes the describe how the compilation is performed.
///
/// This used to selectively emit certain messages depending on how the
/// CFE is invoked. For instance to emit a message about the null safety
/// compilation mode when the modes includes [InvocationMode.compile].
Set<InvocationMode> invocationModes = {};
/// Verbosity level used for filtering emitted messages.
Verbosity verbosity = Verbosity.all;
/// Returns `true` if the experiment with the given [flag] is enabled, either
/// explicitly or implicitly.
///
/// Note that libraries can still opt out of the experiment by having a lower
/// language version than required for the experiment.
bool isExperimentEnabled(ExperimentalFlag flag) {
return flags.isExperimentEnabled(flag,
explicitExperimentalFlags: explicitExperimentalFlags,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting);
}
/// Returns `true` if the experiment with the given [flag] is enabled either
/// explicitly or implicitly for the library with the given [importUri].
///
/// Note that the library can still opt out of the experiment by having a
/// lower language version than required for the experiment. See
/// [getExperimentEnabledVersionInLibrary].
bool isExperimentEnabledInLibrary(ExperimentalFlag flag, Uri importUri) {
return flags.isExperimentEnabledInLibrary(flag, importUri,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting,
explicitExperimentalFlags: explicitExperimentalFlags,
allowedExperimentalFlags: allowedExperimentalFlagsForTesting);
}
/// Returns the minimum language version needed for a library with the given
/// [importUri] to opt in to the experiment with the given [flag].
///
/// Note that the experiment might not be enabled at all for the library, as
/// computed by [isExperimentEnabledInLibrary].
Version getExperimentEnabledVersionInLibrary(
ExperimentalFlag flag, Uri importUri) {
return flags.getExperimentEnabledVersionInLibrary(
flag, importUri, explicitExperimentalFlags,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting,
allowedExperimentalFlags: allowedExperimentalFlagsForTesting,
experimentEnabledVersionForTesting: experimentEnabledVersionForTesting,
experimentReleasedVersionForTesting:
experimentReleasedVersionForTesting);
}
/// Return `true` if the experiment with the given [flag] is enabled for the
/// library with the given [importUri] and language [version].
bool isExperimentEnabledInLibraryByVersion(
ExperimentalFlag flag, Uri importUri, Version version) {
return flags.isExperimentEnabledInLibraryByVersion(flag, importUri, version,
explicitExperimentalFlags: explicitExperimentalFlags,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting,
allowedExperimentalFlags: allowedExperimentalFlagsForTesting,
experimentEnabledVersionForTesting: experimentEnabledVersionForTesting,
experimentReleasedVersionForTesting:
experimentReleasedVersionForTesting);
}
bool equivalent(CompilerOptions other,
{bool ignoreOnDiagnostic: true,
bool ignoreVerbose: true,
bool ignoreVerify: true,
bool ignoreDebugDump: true}) {
if (sdkRoot != other.sdkRoot) return false;
if (librariesSpecificationUri != other.librariesSpecificationUri) {
return false;
}
if (!ignoreOnDiagnostic) {
if (onDiagnostic != other.onDiagnostic) return false;
}
if (packagesFileUri != other.packagesFileUri) return false;
if (!equalLists(additionalDills, other.additionalDills)) return false;
if (sdkSummary != other.sdkSummary) return false;
if (!equalMaps(declaredVariables, other.declaredVariables)) return false;
if (fileSystem != other.fileSystem) return false;
if (compileSdk != compileSdk) return false;
// chaseDependencies aren't used anywhere, so ignored here.
// targetPatches aren't used anywhere, so ignored here.
if (!equalMaps(
explicitExperimentalFlags, other.explicitExperimentalFlags)) {
return false;
}
if (!equalMaps(environmentDefines, other.environmentDefines)) return false;
if (errorOnUnevaluatedConstant != other.errorOnUnevaluatedConstant) {
return false;
}
if (target != other.target) {
if (target.runtimeType != other.target.runtimeType) return false;
if (target?.name != other.target?.name) return false;
if (target?.flags != other.target?.flags) return false;
}
// enableAsserts is not used anywhere, so ignored here.
if (!ignoreVerbose) {
if (verbose != other.verbose) return false;
}
if (!ignoreVerify) {
if (verify != other.verify) return false;
if (skipPlatformVerification != other.skipPlatformVerification) {
return false;
}
}
if (!ignoreDebugDump) {
if (debugDump != other.debugDump) return false;
}
if (omitPlatform != other.omitPlatform) return false;
if (setExitCodeOnProblem != other.setExitCodeOnProblem) return false;
if (embedSourceText != other.embedSourceText) return false;
if (throwOnErrorsForDebugging != other.throwOnErrorsForDebugging) {
return false;
}
if (throwOnWarningsForDebugging != other.throwOnWarningsForDebugging) {
return false;
}
if (skipForDebugging != other.skipForDebugging) return false;
if (writeFileOnCrashReport != other.writeFileOnCrashReport) return false;
if (nnbdMode != other.nnbdMode) return false;
if (currentSdkVersion != other.currentSdkVersion) return false;
if (emitDeps != other.emitDeps) return false;
if (!equalSets(invocationModes, other.invocationModes)) return false;
return true;
}
}
/// Parse experimental flag arguments of the form 'flag' or 'no-flag' into a map
/// from 'flag' to `true` or `false`, respectively.
Map<String, bool> parseExperimentalArguments(List<String>? arguments) {
Map<String, bool> result = {};
if (arguments != null) {
for (String argument in arguments) {
for (String feature in argument.split(',')) {
if (feature.startsWith('no-')) {
result[feature.substring(3)] = false;
} else {
result[feature] = true;
}
}
}
}
return result;
}
/// Parse a map of experimental flags to values that can be passed to
/// [CompilerOptions.explicitExperimentalFlags].
///
/// If an unknown flag is mentioned, or a flag is mentioned more than once,
/// the supplied error handler is called with an error message.
///
/// If an expired flag is set to its non-default value the supplied error
/// handler is called with an error message.
///
/// If an expired flag is set to its default value the supplied warning
/// handler is called with a warning message.
Map<ExperimentalFlag, bool> parseExperimentalFlags(
Map<String, bool>? experiments,
{required void Function(String message) onError,
void Function(String message)? onWarning}) {
Map<ExperimentalFlag, bool> flags = <ExperimentalFlag, bool>{};
if (experiments != null) {
for (String experiment in experiments.keys) {
bool value = experiments[experiment]!;
ExperimentalFlag? flag = parseExperimentalFlag(experiment);
if (flag == null) {
onError("Unknown experiment: " + experiment);
} else if (flags.containsKey(flag)) {
if (flags[flag] != value) {
onError(
"Experiment specified with conflicting values: " + experiment);
}
} else {
if (expiredExperimentalFlags[flag]!) {
if (value != defaultExperimentalFlags[flag]) {
/// Produce an error when the value is not the default value.
if (value) {
onError("Enabling experiment " +
experiment +
" is no longer supported.");
} else {
onError("Disabling experiment " +
experiment +
" is no longer supported.");
}
value = defaultExperimentalFlags[flag]!;
} else if (onWarning != null) {
/// Produce a warning when the value is the default value.
if (value) {
onWarning("Experiment " +
experiment +
" is enabled by default. "
"The use of the flag is deprecated.");
} else {
onWarning("Experiment " +
experiment +
" is disabled by default. "
"The use of the flag is deprecated.");
}
}
flags[flag] = value;
} else {
flags[flag] = value;
}
}
}
}
return flags;
}
class InvocationMode {
/// This mode is used for when the CFE is invoked in order to compile an
/// executable.
///
/// If used, a message about the null safety compilation mode will be emitted.
static const InvocationMode compile = const InvocationMode('compile');
final String name;
const InvocationMode(this.name);
/// Returns the set of information modes from a comma-separated list of
/// invocation mode names.
///
/// If a name isn't recognized and [onError] is provided, [onError] is called
/// with an error messages and an empty set of invocation modes is returned.
///
/// If a name isn't recognized and [onError] isn't provided, an error is
/// thrown.
static Set<InvocationMode> parseArguments(String arg,
{void Function(String)? onError}) {
Set<InvocationMode> result = {};
for (String name in arg.split(',')) {
if (name.isNotEmpty) {
InvocationMode? mode = fromName(name);
if (mode == null) {
String message = "Unknown invocation mode '$name'.";
if (onError != null) {
onError(message);
} else {
throw new UnsupportedError(message);
}
} else {
result.add(mode);
}
}
}
return result;
}
/// Returns the [InvocationMode] with the given [name].
static InvocationMode? fromName(String name) {
for (InvocationMode invocationMode in values) {
if (name == invocationMode.name) {
return invocationMode;
}
}
return null;
}
static const List<InvocationMode> values = const [compile];
}
/// Verbosity level used for filtering messages during compilation.
class Verbosity {
/// Only error messages are emitted.
static const Verbosity error =
const Verbosity('error', 'Show only error messages');
/// Error and warning messages are emitted.
static const Verbosity warning =
const Verbosity('warning', 'Show only error and warning messages');
/// Error, warning, and info messages are emitted.
static const Verbosity info =
const Verbosity('info', 'Show error, warning, and info messages');
/// All messages are emitted.
static const Verbosity all = const Verbosity('all', 'Show all messages');
static const List<Verbosity> values = const [error, warning, info, all];
/// Returns the names of all options.
static List<String> get allowedValues =>
[for (Verbosity value in values) value.name];
/// Returns a map from option name to option help messages.
static Map<String, String> get allowedValuesHelp =>
{for (Verbosity value in values) value.name: value.help};
/// Returns the verbosity corresponding to the given [name].
///
/// If [name] isn't recognized and [onError] is provided, [onError] is called
/// with an error messages and [defaultValue] is returned.
///
/// If [name] isn't recognized and [onError] isn't provided, an error is
/// thrown.
static Verbosity parseArgument(String name,
{void Function(String)? onError, Verbosity defaultValue: Verbosity.all}) {
for (Verbosity verbosity in values) {
if (name == verbosity.name) {
return verbosity;
}
}
String message = "Unknown verbosity '$name'.";
if (onError != null) {
onError(message);
return defaultValue;
}
throw new UnsupportedError(message);
}
static bool shouldPrint(Verbosity verbosity, DiagnosticMessage message) {
Severity severity = message.severity;
switch (verbosity) {
case Verbosity.error:
switch (severity) {
case Severity.internalProblem:
case Severity.error:
return true;
case Severity.warning:
case Severity.info:
case Severity.context:
case Severity.ignored:
return false;
}
case Verbosity.warning:
switch (severity) {
case Severity.internalProblem:
case Severity.error:
case Severity.warning:
return true;
case Severity.info:
case Severity.context:
case Severity.ignored:
return false;
}
case Verbosity.info:
switch (severity) {
case Severity.internalProblem:
case Severity.error:
case Severity.warning:
case Severity.info:
return true;
case Severity.context:
case Severity.ignored:
return false;
}
case Verbosity.all:
return true;
}
throw new UnsupportedError(
"Unsupported verbosity $verbosity and severity $severity.");
}
static const String defaultValue = 'all';
final String name;
final String help;
const Verbosity(this.name, this.help);
@override
String toString() => 'Verbosity($name)';
}