blob: 7b29209b148aeebaf65c93c915ea4992462743e7 [file] [log] [blame]
// Copyright (c) 2019, 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.
import 'dart:io' show File;
import 'package:_fe_analyzer_shared/src/scanner/characters.dart'
show $A, $MINUS, $a, $z;
import 'package:_fe_analyzer_shared/src/sdk/allowed_experiments.dart';
import 'package:dart_style/dart_style.dart' show DartFormatter;
import 'package:yaml/yaml.dart' show YamlMap, loadYaml;
import '../../test/utils/io_utils.dart' show computeRepoDirUri;
void main(List<String> arguments) {
final Uri repoDir = computeRepoDirUri();
new File.fromUri(computeCfeGeneratedFile(repoDir))
.writeAsStringSync(generateCfeFile(repoDir), flush: true);
new File.fromUri(computeKernelGeneratedFile(repoDir))
.writeAsStringSync(generateKernelFile(repoDir), flush: true);
}
Uri computeCfeGeneratedFile(Uri repoDir) {
return repoDir.resolve(
"pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart");
}
Uri computeKernelGeneratedFile(Uri repoDir) {
return repoDir.resolve("pkg/kernel/lib/default_language_version.dart");
}
Uri computeYamlFile(Uri repoDir) {
return repoDir.resolve("tools/experimental_features.yaml");
}
Uri computeAllowListFile(Uri repoDir) {
return repoDir.resolve("sdk/lib/_internal/allowed_experiments.json");
}
String generateKernelFile(Uri repoDir) {
Uri yamlFile = computeYamlFile(repoDir);
Map<dynamic, dynamic> yaml =
loadYaml(new File.fromUri(yamlFile).readAsStringSync());
int currentVersionMajor;
int currentVersionMinor;
{
String currentVersion = getAsVersionNumberString(yaml['current-version'])!;
List<String> split = currentVersion.split(".");
currentVersionMajor = int.parse(split[0]);
currentVersionMinor = int.parse(split[1]);
}
StringBuffer sb = new StringBuffer();
sb.write('''
// Copyright (c) 2021, 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.
// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
//
// Instead modify 'tools/experimental_features.yaml' and run
// 'dart pkg/front_end/tool/fasta.dart generate-experimental-flags' to update.
import "ast.dart";
Version defaultLanguageVersion = const Version($currentVersionMajor, $currentVersionMinor);
''');
return new DartFormatter().format("$sb");
}
String generateCfeFile(Uri repoDir) {
Uri yamlFile = computeYamlFile(repoDir);
Map<dynamic, dynamic> yaml =
loadYaml(new File.fromUri(yamlFile).readAsStringSync());
int currentVersionMajor;
int currentVersionMinor;
{
String currentVersion = getAsVersionNumberString(yaml['current-version'])!;
List<String> split = currentVersion.split(".");
currentVersionMajor = int.parse(split[0]);
currentVersionMinor = int.parse(split[1]);
}
StringBuffer sb = new StringBuffer();
sb.write('''
// Copyright (c) 2021, 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.
// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
//
// Instead modify 'tools/experimental_features.yaml' and run
// 'dart pkg/front_end/tool/fasta.dart generate-experimental-flags' to update.
part of 'experimental_flags.dart';
''');
Map<String, dynamic> features = {};
Map<dynamic, dynamic> yamlFeatures = yaml['features'];
for (MapEntry<dynamic, dynamic> entry in yamlFeatures.entries) {
String category = entry.value["category"] ?? "language";
if (category != "language" && category != "CFE") {
// Skip a feature with a category that's not language or CFE.
// In the future we might want to generate different code for different
// things.
continue;
}
features[entry.key] = entry.value;
}
List<String> keys = features.keys.toList()..sort();
sb.write('''
/// An experiment flag including its fixed properties.
class ExperimentalFlag {
/// The name of this flag as used in the --enable-experiment option.
final String name;
/// `true` if this experimental feature is enabled by default.
///
/// When `true`, the feature can still be disabled in individual libraries
/// with a language version below the [experimentEnabledVersion], and if not
/// [isExpired], the feature can also be disabled by using a 'no-' prefix
/// in the --enable-experiment option.
final bool isEnabledByDefault;
/// `true` if this feature can no longer be changed using the
/// --enable-experiment option.
///
/// Libraries can still opt out of the feature by using a language version
/// below the [experimentEnabledVersion].
final bool isExpired;
final Version enabledVersion;
/// The minimum version that supports this feature.
///
/// If the feature is not enabled by default, this is the current language
/// version.
final Version experimentEnabledVersion;
/// The minimum version that supports this feature in allowed libraries.
///
/// Allowed libraries are specified in
///
/// sdk/lib/_internal/allowed_experiments.json
final Version experimentReleasedVersion;
const ExperimentalFlag(
{required this.name,
required this.isEnabledByDefault,
required this.isExpired,
required this.enabledVersion,
required this.experimentEnabledVersion,
required this.experimentReleasedVersion});
''');
for (String key in keys) {
String identifier = keyToIdentifier(key);
int enabledInMajor;
int enabledInMinor;
String? enabledIn =
getAsVersionNumberString((features[key] as YamlMap)['enabledIn']);
if (enabledIn == null) {
enabledInMajor = currentVersionMajor;
enabledInMinor = currentVersionMinor;
} else {
List<String> split = enabledIn.split(".");
enabledInMajor = int.parse(split[0]);
enabledInMinor = int.parse(split[1]);
}
bool? expired = (features[key] as YamlMap)['expired'];
bool shipped = (features[key] as YamlMap)['enabledIn'] != null;
if (shipped) {
if (expired == false) {
throw 'Cannot mark shipped feature "$key" as "expired: false"';
}
}
int releaseMajor;
int releaseMinor;
String? experimentalReleaseVersion = getAsVersionNumberString(
(features[key] as YamlMap)['experimentalReleaseVersion']);
if (experimentalReleaseVersion != null) {
List<String> split = experimentalReleaseVersion.split(".");
releaseMajor = int.parse(split[0]);
releaseMinor = int.parse(split[1]);
} else if (enabledIn != null) {
List<String> split = enabledIn.split(".");
releaseMajor = int.parse(split[0]);
releaseMinor = int.parse(split[1]);
} else {
releaseMajor = currentVersionMajor;
releaseMinor = currentVersionMinor;
}
sb.writeln('''
static const ExperimentalFlag ${identifier} =
const ExperimentalFlag(
name: '$key',
isEnabledByDefault: $shipped,
isExpired: ${expired == true},
enabledVersion: const Version($enabledInMajor, $enabledInMinor),
experimentEnabledVersion: const Version($enabledInMajor, $enabledInMinor),
experimentReleasedVersion: const Version($releaseMajor, $releaseMinor));
''');
}
sb.write('''
}
''');
sb.write('''
/// Interface for accessing the global state of experimental features.
class GlobalFeatures {
final Map<ExperimentalFlag, bool> explicitExperimentalFlags;
final AllowedExperimentalFlags? allowedExperimentalFlags;
final Map<ExperimentalFlag, bool>? defaultExperimentFlagsForTesting;
final Map<ExperimentalFlag, Version>? experimentEnabledVersionForTesting;
final Map<ExperimentalFlag, Version>? experimentReleasedVersionForTesting;
GlobalFeatures(this.explicitExperimentalFlags,
{this.allowedExperimentalFlags,
this.defaultExperimentFlagsForTesting,
this.experimentEnabledVersionForTesting,
this.experimentReleasedVersionForTesting});
GlobalFeature _computeGlobalFeature(ExperimentalFlag flag) {
return new GlobalFeature(
flag,
isExperimentEnabled(flag,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting,
explicitExperimentalFlags: explicitExperimentalFlags));
}
LibraryFeature _computeLibraryFeature(
ExperimentalFlag flag, Uri canonicalUri, Version libraryVersion) {
return new LibraryFeature(
flag,
isExperimentEnabledInLibrary(flag, canonicalUri,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting,
explicitExperimentalFlags: explicitExperimentalFlags,
allowedExperimentalFlags: allowedExperimentalFlags),
getExperimentEnabledVersionInLibrary(
flag, canonicalUri, explicitExperimentalFlags,
allowedExperimentalFlags: allowedExperimentalFlags,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting,
experimentEnabledVersionForTesting:
experimentEnabledVersionForTesting,
experimentReleasedVersionForTesting:
experimentReleasedVersionForTesting),
isExperimentEnabledInLibraryByVersion(
flag, canonicalUri, libraryVersion,
defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting,
explicitExperimentalFlags: explicitExperimentalFlags,
allowedExperimentalFlags: allowedExperimentalFlags));
}
''');
for (String key in keys) {
String identifier = keyToIdentifier(key);
sb.write('''
GlobalFeature? _${identifier};
GlobalFeature get ${identifier} =>
_${identifier} ??= _computeGlobalFeature(ExperimentalFlag.${identifier});
''');
}
sb.write('''
}
/// Interface for accessing the state of experimental features within a
/// specific library.
class LibraryFeatures {
final GlobalFeatures globalFeatures;
final Uri canonicalUri;
final Version libraryVersion;
LibraryFeatures(this.globalFeatures, this.canonicalUri, this.libraryVersion);
''');
for (String key in keys) {
String identifier = keyToIdentifier(key);
sb.write('''
LibraryFeature? _${identifier};
LibraryFeature get ${identifier} => _${identifier} ??= globalFeatures
._computeLibraryFeature(
ExperimentalFlag.${identifier},
canonicalUri,
libraryVersion);
''');
}
sb.write('''
}
''');
sb.write('''
ExperimentalFlag? parseExperimentalFlag(String flag) {
switch (flag) {
''');
for (String key in keys) {
sb.writeln(' case "$key":');
sb.writeln(' return ExperimentalFlag.${keyToIdentifier(key)};');
}
sb.write(''' }
return null;
}
final Map<ExperimentalFlag, bool> defaultExperimentalFlags = {
''');
for (String key in keys) {
sb.writeln('''
ExperimentalFlag.${keyToIdentifier(key)}:
ExperimentalFlag.${keyToIdentifier(key)}.isEnabledByDefault,''');
}
sb.write('''
};
''');
Uri allowListFile = computeAllowListFile(repoDir);
AllowedExperiments allowedExperiments = parseAllowedExperiments(
new File.fromUri(allowListFile).readAsStringSync());
sb.write('''
const AllowedExperimentalFlags defaultAllowedExperimentalFlags =
const AllowedExperimentalFlags(
''');
sb.writeln('sdkDefaultExperiments: {');
for (String sdkDefaultExperiment
in allowedExperiments.sdkDefaultExperiments) {
sb.writeln('ExperimentalFlag.${keyToIdentifier(sdkDefaultExperiment)},');
}
sb.writeln('},');
sb.writeln('sdkLibraryExperiments: {');
allowedExperiments.sdkLibraryExperiments
.forEach((String library, List<String> experiments) {
sb.writeln('"$library": {');
for (String experiment in experiments) {
sb.writeln('ExperimentalFlag.${keyToIdentifier(experiment)},');
}
sb.writeln('},');
});
sb.writeln('},');
sb.writeln('packageExperiments: {');
allowedExperiments.packageExperiments
.forEach((String package, List<String> experiments) {
sb.writeln('"$package": {');
for (String experiment in experiments) {
sb.writeln('ExperimentalFlag.${keyToIdentifier(experiment)},');
}
sb.writeln('},');
});
sb.writeln('});');
return new DartFormatter().format("$sb");
}
String keyToIdentifier(String key, {bool upperCaseFirst = false}) {
StringBuffer identifier = StringBuffer();
bool first = true;
for (int index = 0; index < key.length; ++index) {
int code = key.codeUnitAt(index);
if (code == $MINUS) {
++index;
code = key.codeUnitAt(index);
if ($a <= code && code <= $z) {
code = code - $a + $A;
}
}
if (first && upperCaseFirst && $a <= code && code <= $z) {
code = code - $a + $A;
}
first = false;
identifier.writeCharCode(code);
}
return identifier.toString();
}
String? getAsVersionNumberString(dynamic value) {
if (value == null) return null;
if (value is String) return value;
if (value is double) return "$value";
throw "Unexpected value: $value (${value.runtimeType})";
}