diff --git a/extensions/scarb-snforge-test-collector/src/metadata.rs b/extensions/scarb-snforge-test-collector/src/metadata.rs index edd8c1590..d863154a0 100644 --- a/extensions/scarb-snforge-test-collector/src/metadata.rs +++ b/extensions/scarb-snforge-test-collector/src/metadata.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, ensure, Context, Result}; -use cairo_lang_filesystem::db::{CrateSettings, Edition}; +use cairo_lang_filesystem::db::{CrateSettings, Edition, ExperimentalFeaturesConfig}; use cairo_lang_project::AllCratesConfig; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use camino::{Utf8Path, Utf8PathBuf}; @@ -109,24 +109,25 @@ impl CompilationUnit<'_> { .components .iter() .map(|component| { + let pkg = self + .metadata + .get_package(&component.package) + .unwrap_or_else(|| panic!("Failed to find = {} package", &component.package)); ( SmolStr::from(&component.name), CrateSettings { - edition: if let Some(edition) = self - .metadata - .get_package(&component.package) - .unwrap_or_else(|| { - panic!("Failed to find = {} package", component.package) - }) - .edition - .clone() - { + edition: if let Some(edition) = pkg.edition.clone() { let edition_value = serde_json::Value::String(edition); serde_json::from_value(edition_value).unwrap() } else { Edition::default() }, - experimental_features: Default::default(), + // TODO (#1040): replace this with a macro + experimental_features: ExperimentalFeaturesConfig { + negative_impls: pkg + .experimental_features + .contains(&String::from("negative_impls")), + }, }, ) }) diff --git a/scarb-metadata/src/lib.rs b/scarb-metadata/src/lib.rs index 5c8bd5f11..3aaf0cdbf 100644 --- a/scarb-metadata/src/lib.rs +++ b/scarb-metadata/src/lib.rs @@ -231,6 +231,9 @@ pub struct PackageMetadata { #[serde(flatten)] pub manifest_metadata: ManifestMetadata, + /// Compiler experimental features allowed for this package. + pub experimental_features: Vec, + /// Additional data not captured by deserializer. #[cfg_attr(feature = "builder", builder(default))] #[serde(flatten)] diff --git a/scarb/src/compiler/db.rs b/scarb/src/compiler/db.rs index 84a2de99a..493694917 100644 --- a/scarb/src/compiler/db.rs +++ b/scarb/src/compiler/db.rs @@ -93,16 +93,21 @@ fn build_project_config(unit: &CompilationUnit) -> Result { .components .iter() .map(|component| { + let experimental_features = component.package.manifest.experimental_features.clone(); ( component.cairo_package_name(), CrateSettings { edition: component.package.manifest.edition, - experimental_features: Default::default(), + // TODO (#1040): replace this with a macro + experimental_features: cairo_lang_filesystem::db::ExperimentalFeaturesConfig { + negative_impls: experimental_features + .unwrap_or_default() + .contains(&SmolStr::new_inline("negative_impls")), + }, }, ) }) .collect(); - let crates_config = AllCratesConfig { override_map: crates_config, ..Default::default() diff --git a/scarb/src/core/manifest/mod.rs b/scarb/src/core/manifest/mod.rs index d33ebdc6f..b3c7d1df2 100644 --- a/scarb/src/core/manifest/mod.rs +++ b/scarb/src/core/manifest/mod.rs @@ -49,6 +49,9 @@ pub struct Manifest { pub compiler_config: ManifestCompilerConfig, #[builder(default)] pub scripts: BTreeMap, + /// Allow experimental features. + #[builder(default)] + pub experimental_features: Option>, } /// Subset of a [`Manifest`] that contains package metadata. diff --git a/scarb/src/core/manifest/toml_manifest.rs b/scarb/src/core/manifest/toml_manifest.rs index 5fbf580cf..4d286f9b0 100644 --- a/scarb/src/core/manifest/toml_manifest.rs +++ b/scarb/src/core/manifest/toml_manifest.rs @@ -198,6 +198,7 @@ pub struct TomlPackage { /// **UNSTABLE** This package does not depend on Cairo's `core`. pub no_core: Option, pub cairo_version: Option>, + pub experimental_features: Option>, } #[derive(Clone, Debug, Serialize, Eq, PartialEq)] @@ -556,6 +557,8 @@ impl TomlManifest { .transpose()? .unwrap_or_default(); + // TODO (#1040): add checking for fields that are not present in ExperimentalFeaturesConfig + let experimental_features = package.experimental_features.clone(); let manifest = ManifestBuilder::default() .summary(summary) .targets(targets) @@ -563,8 +566,8 @@ impl TomlManifest { .metadata(metadata) .compiler_config(compiler_config) .scripts(scripts) + .experimental_features(experimental_features) .build()?; - Ok(manifest) } diff --git a/scarb/src/core/publishing/manifest_normalization.rs b/scarb/src/core/publishing/manifest_normalization.rs index 993534798..6aa3b81c4 100644 --- a/scarb/src/core/publishing/manifest_normalization.rs +++ b/scarb/src/core/publishing/manifest_normalization.rs @@ -75,6 +75,7 @@ fn generate_package(pkg: &Package) -> Box { repository: metadata.repository.clone().map(MaybeWorkspace::Defined), no_core: summary.no_core.then_some(true), cairo_version: metadata.cairo_version.clone().map(MaybeWorkspace::Defined), + experimental_features: pkg.manifest.experimental_features.clone(), }) } diff --git a/scarb/src/ops/metadata.rs b/scarb/src/ops/metadata.rs index 0ec9ab137..008303e9a 100644 --- a/scarb/src/ops/metadata.rs +++ b/scarb/src/ops/metadata.rs @@ -135,6 +135,15 @@ fn collect_package_metadata(package: &Package) -> m::PackageMetadata { let edition = edition_variant(package.manifest.edition); + let experimental_features: Vec = package + .manifest + .experimental_features + .clone() + .unwrap_or_default() + .iter() + .map(|x| x.to_string()) + .collect(); + m::PackageMetadataBuilder::default() .id(wrap_package_id(package.id)) .name(package.id.name.clone()) @@ -146,6 +155,7 @@ fn collect_package_metadata(package: &Package) -> m::PackageMetadata { .dependencies(dependencies) .targets(targets) .manifest_metadata(manifest_metadata) + .experimental_features(experimental_features) .build() .unwrap() } diff --git a/scarb/tests/metadata.rs b/scarb/tests/metadata.rs index 0eff0d1a3..ad26c40f9 100644 --- a/scarb/tests/metadata.rs +++ b/scarb/tests/metadata.rs @@ -1281,3 +1281,28 @@ fn includes_edition() { } panic!("Package not found in metadata!"); } + +#[test] +fn includes_experimental_features() { + let t = assert_fs::TempDir::new().unwrap(); + ProjectBuilder::start() + .name("hello") + .version("0.1.0") + .manifest_package_extra(r#"experimental-features = ["negative_impls"]"#) + .build(&t); + + let metadata = Scarb::quick_snapbox() + .arg("--json") + .arg("metadata") + .arg("--format-version") + .arg("1") + .current_dir(&t) + .stdout_json::(); + + assert!(packages_by_name(metadata) + .get("hello") + .unwrap() + .clone() + .experimental_features + .contains(&String::from("negative_impls"))) +} diff --git a/utils/scarb-test-support/src/project_builder.rs b/utils/scarb-test-support/src/project_builder.rs index 8bf4154ee..7f417bd0b 100644 --- a/utils/scarb-test-support/src/project_builder.rs +++ b/utils/scarb-test-support/src/project_builder.rs @@ -24,6 +24,7 @@ pub struct ProjectBuilder { src: HashMap, deps: Vec<(String, Value)>, dev_deps: Vec<(String, Value)>, + manifest_package_extra: String, manifest_extra: String, } @@ -44,6 +45,7 @@ impl ProjectBuilder { )]), deps: Vec::new(), dev_deps: Vec::new(), + manifest_package_extra: String::new(), manifest_extra: String::new(), } } @@ -95,6 +97,11 @@ impl ProjectBuilder { self.dep("starknet", Dep.version(CAIRO_VERSION)) } + pub fn manifest_package_extra(mut self, extra: impl ToString) -> Self { + self.manifest_package_extra = extra.to_string(); + self + } + pub fn manifest_extra(mut self, extra: impl ToString) -> Self { self.manifest_extra = extra.to_string(); self @@ -111,6 +118,12 @@ impl ProjectBuilder { if let Some(cairo_version) = self.cairo_version.as_ref() { doc["package"]["cairo-version"] = Item::Value(Value::from(cairo_version.to_string())); } + let mut manifest = doc.to_string(); + if !self.manifest_package_extra.is_empty() { + manifest.push_str(&self.manifest_package_extra); + } + + let mut doc = manifest.parse::().unwrap(); doc["dependencies"] = toml_edit::table(); for (name, dep) in &self.deps { doc["dependencies"][name.clone()] = Item::Value(dep.clone()); diff --git a/website/docs/reference/manifest.md b/website/docs/reference/manifest.md index c414e6234..e7ad3d820 100644 --- a/website/docs/reference/manifest.md +++ b/website/docs/reference/manifest.md @@ -209,6 +209,15 @@ Keys are human-readable link names, and values are URLs. "We're hiring" = "https://swmansion.com/careers/" ``` +### `experimental-features` + +This field is responsible for setting experimental flags to be used on the package for the compiler. + +```toml +[package] +experimental-features = ["negative_impls"] +``` + ## `[dependencies]` See [Specifying Dependencies](./specifying-dependencies) page.