Skip to content

Commit 2d68b15

Browse files
authored
Add stability attributes to the worlds (bytecodealliance#2194)
Signed-off-by: James Sturtevant <[email protected]>
1 parent 163a6af commit 2d68b15

File tree

3 files changed

+144
-20
lines changed

3 files changed

+144
-20
lines changed

crates/wit-smith/src/generate.rs

Lines changed: 142 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct InterfaceGenerator<'a> {
3030
unique_names: HashSet<String>,
3131
types_in_interface: Vec<Type>,
3232
package_name: &'a str,
33+
version: Option<Version>,
3334
}
3435

3536
#[derive(Clone)]
@@ -147,7 +148,7 @@ impl Generator {
147148
name: PackageName {
148149
namespace,
149150
name: package_name.clone(),
150-
version,
151+
version: version.clone(),
151152
},
152153
file: File::default(),
153154
sources: SourceMap::new(),
@@ -195,7 +196,8 @@ impl Generator {
195196
let world_name =
196197
file.gen_unique_package_name(u, &mut package_names, DefinitionKind::World)?;
197198
log::debug!("new world `{world_name}` in {i}");
198-
let world = self.gen_world(u, &world_name, file, &package_name)?;
199+
let world =
200+
self.gen_world(u, &world_name, file, &package_name, version.clone())?;
199201
file.items.push(world);
200202

201203
// Insert the world at the package and file level, asserting
@@ -227,7 +229,7 @@ impl Generator {
227229
let id = self.next_interface_id;
228230
self.next_interface_id += 1;
229231
let (src, types) =
230-
self.gen_interface(u, Some(&name), file, &package_name, None)?;
232+
self.gen_interface(u, Some(&name), file, &package_name, None, None)?;
231233
file.items.push(src);
232234
if types.is_empty() {
233235
continue;
@@ -352,8 +354,9 @@ impl Generator {
352354
name: &str,
353355
file: &mut File,
354356
package_name: &str,
357+
version: Option<Version>,
355358
) -> Result<String> {
356-
InterfaceGenerator::new(self, file, package_name).gen_world(u, name)
359+
InterfaceGenerator::new(self, file, package_name, version).gen_world(u, name)
357360
}
358361

359362
fn gen_interface(
@@ -363,8 +366,9 @@ impl Generator {
363366
file: &mut File,
364367
package_name: &str,
365368
world_name: Option<&str>,
369+
version: Option<Version>,
366370
) -> Result<(String, Vec<Type>)> {
367-
let mut generator = InterfaceGenerator::new(self, file, package_name);
371+
let mut generator = InterfaceGenerator::new(self, file, package_name, version);
368372
let ret = generator.gen_interface(u, name, world_name)?;
369373
Ok((ret, generator.types_in_interface))
370374
}
@@ -495,6 +499,7 @@ impl<'a> InterfaceGenerator<'a> {
495499
generator: &'a mut Generator,
496500
file: &'a mut File,
497501
package_name: &'a str,
502+
version: Option<Version>,
498503
) -> InterfaceGenerator<'a> {
499504
InterfaceGenerator {
500505
generator,
@@ -504,6 +509,48 @@ impl<'a> InterfaceGenerator<'a> {
504509
// ABI always using a linear memory named `memory`.
505510
unique_names: HashSet::from_iter(["memory".to_string()]),
506511
package_name: package_name,
512+
version,
513+
}
514+
}
515+
516+
// Generate a feature gate annotation (@since, @unstable, or @deprecated)
517+
// If version is provided, ensures the annotation is compatible with the version
518+
fn gen_feature_annotation(&self, u: &mut Unstructured<'_>) -> Result<Option<String>> {
519+
if u.arbitrary()? {
520+
return Ok(None);
521+
}
522+
523+
let feature_names = ["active", "inactive"];
524+
#[derive(Arbitrary)]
525+
enum AnnotationType {
526+
Since,
527+
Unstable,
528+
Deprecated,
529+
}
530+
531+
match self.version {
532+
None => {
533+
// No package version available
534+
return Ok(None);
535+
}
536+
Some(_) => match u.arbitrary()? {
537+
AnnotationType::Since => {
538+
let v = gen_version_less_than(u, &self.version)?;
539+
Ok(Some(format!("@since(version = {v})")))
540+
}
541+
AnnotationType::Unstable => {
542+
let feature = u.choose(&feature_names)?;
543+
Ok(Some(format!("@unstable(feature = {feature})")))
544+
}
545+
AnnotationType::Deprecated => {
546+
let depreciation_version = gen_version_less_than(u, &self.version)?;
547+
let since_version =
548+
gen_version_less_than(u, &Some(depreciation_version.clone()))?;
549+
Ok(Some(format!(
550+
"@deprecated(version = {depreciation_version})\n@since(version = {since_version})",
551+
)))
552+
}
553+
},
507554
}
508555
}
509556

@@ -514,6 +561,12 @@ impl<'a> InterfaceGenerator<'a> {
514561
world_name: Option<&str>,
515562
) -> Result<String> {
516563
let mut ret = String::new();
564+
565+
if let Some(annotation) = self.gen_feature_annotation(u)? {
566+
ret.push_str(&annotation);
567+
ret.push_str("\n");
568+
}
569+
517570
ret.push_str("interface ");
518571
if let Some(name) = name {
519572
ret.push_str("%");
@@ -576,7 +629,7 @@ impl<'a> InterfaceGenerator<'a> {
576629
ret.push_str(world_name);
577630
ret.push_str(" {\n");
578631

579-
#[derive(Arbitrary, Copy, Clone)]
632+
#[derive(Arbitrary, Copy, Clone, Debug)]
580633
enum Direction {
581634
Import,
582635
Export,
@@ -615,6 +668,12 @@ impl<'a> InterfaceGenerator<'a> {
615668
};
616669

617670
let mut part = String::new();
671+
672+
if let Some(annotation) = self.gen_feature_annotation(u)? {
673+
part.push_str(&annotation);
674+
part.push_str("\n");
675+
}
676+
618677
if let Some(dir) = direction {
619678
part.push_str(match dir {
620679
Direction::Import => "import ",
@@ -686,15 +745,14 @@ impl<'a> InterfaceGenerator<'a> {
686745
}
687746
ItemKind::AnonInterface(_) => {
688747
let iface =
689-
InterfaceGenerator::new(self.generator, self.file, self.package_name)
748+
InterfaceGenerator::new(self.generator, self.file, self.package_name, None)
690749
.gen_interface(u, None, Some(world_name))?;
691750
part.push_str(&iface);
692751
}
693752

694753
ItemKind::Type => {
695754
let name = name.unwrap();
696755
let (ty, typedef) = self.gen_typedef(u, &name)?;
697-
assert!(part.is_empty());
698756
part = typedef;
699757
let is_resource = ty.is_resource;
700758
self.types_in_interface.push(ty);
@@ -803,20 +861,41 @@ impl<'a> InterfaceGenerator<'a> {
803861
Item::Constructor if has_constructor => {}
804862
Item::Constructor => {
805863
has_constructor = true;
806-
let mut part = format!("constructor");
864+
let mut part = String::new();
865+
866+
if let Some(annotation) = self.gen_feature_annotation(u)? {
867+
part.push_str(&annotation);
868+
part.push_str("\n");
869+
}
870+
871+
part.push_str("constructor");
807872
self.gen_params(u, &mut part, false)?;
808873
part.push_str(";");
809874
parts.push(part);
810875
}
811876
Item::Static => {
812-
let mut part = format!("%");
877+
let mut part = String::new();
878+
879+
if let Some(annotation) = self.gen_feature_annotation(u)? {
880+
part.push_str(&annotation);
881+
part.push_str("\n");
882+
}
883+
884+
part.push_str("%");
813885
part.push_str(&gen_unique_name(u, &mut names)?);
814886
part.push_str(": static ");
815887
self.gen_func_sig(u, &mut part, false)?;
816888
parts.push(part);
817889
}
818890
Item::Method => {
819-
let mut part = format!("%");
891+
let mut part = String::new();
892+
893+
if let Some(annotation) = self.gen_feature_annotation(u)? {
894+
part.push_str(&annotation);
895+
part.push_str("\n");
896+
}
897+
898+
part.push_str("%");
820899
part.push_str(&gen_unique_name(u, &mut names)?);
821900
part.push_str(": ");
822901
self.gen_func_sig(u, &mut part, true)?;
@@ -840,6 +919,11 @@ impl<'a> InterfaceGenerator<'a> {
840919
part: &mut String,
841920
world_name: Option<&str>,
842921
) -> Result<bool> {
922+
if let Some(annotation) = self.gen_feature_annotation(u)? {
923+
part.push_str(&annotation);
924+
part.push_str("\n");
925+
}
926+
843927
let mut path = String::new();
844928
let (_name, _id, types) =
845929
match self.generator.gen_interface_path(u, self.file, &mut path)? {
@@ -893,6 +977,12 @@ impl<'a> InterfaceGenerator<'a> {
893977

894978
let mut fuel = self.generator.config.max_type_size;
895979
let mut ret = String::new();
980+
981+
if let Some(annotation) = self.gen_feature_annotation(u)? {
982+
ret.push_str(&annotation);
983+
ret.push_str("\n");
984+
}
985+
896986
let mut is_resource = false;
897987
match u.arbitrary()? {
898988
Kind::Record => {
@@ -1148,7 +1238,14 @@ impl<'a> InterfaceGenerator<'a> {
11481238
}
11491239

11501240
fn gen_func(&mut self, u: &mut Unstructured<'_>) -> Result<String> {
1151-
let mut ret = "%".to_string();
1241+
let mut ret = String::new();
1242+
1243+
if let Some(annotation) = self.gen_feature_annotation(u)? {
1244+
ret.push_str(&annotation);
1245+
ret.push_str("\n");
1246+
}
1247+
1248+
ret.push_str("%");
11521249
ret.push_str(&self.gen_unique_name(u)?);
11531250
ret.push_str(": ");
11541251
self.gen_func_sig(u, &mut ret, false)?;
@@ -1351,20 +1448,45 @@ impl File {
13511448
}
13521449
}
13531450

1354-
fn gen_version(u: &mut Unstructured<'_>) -> Result<Version> {
1355-
Ok(Version {
1356-
major: u.int_in_range(0..=10)?,
1357-
minor: u.int_in_range(0..=10)?,
1358-
patch: u.int_in_range(0..=10)?,
1359-
pre: if u.arbitrary()? {
1451+
fn gen_version_less_than(
1452+
u: &mut Unstructured<'_>,
1453+
existing_version: &Option<Version>,
1454+
) -> Result<Version> {
1455+
const MAX_VERSION_RANGE: u64 = 10;
1456+
let (major, minor, patch) = match existing_version {
1457+
Some(v) => (v.major, v.minor, v.patch),
1458+
None => (MAX_VERSION_RANGE, MAX_VERSION_RANGE, MAX_VERSION_RANGE),
1459+
};
1460+
1461+
let new_version = Version {
1462+
major: u.int_in_range(0..=major)?,
1463+
minor: u.int_in_range(0..=minor)?,
1464+
patch: u.int_in_range(0..=patch)?,
1465+
pre: if (u.arbitrary()? && existing_version.is_none())
1466+
|| existing_version.as_ref().is_some_and(|x| !x.pre.is_empty())
1467+
{
13601468
semver::Prerelease::new("alpha.0").unwrap()
13611469
} else {
13621470
semver::Prerelease::EMPTY
13631471
},
1364-
build: if u.arbitrary()? {
1472+
build: if (u.arbitrary()? && existing_version.is_none())
1473+
|| existing_version
1474+
.as_ref()
1475+
.is_some_and(|x| !x.build.is_empty())
1476+
{
13651477
semver::BuildMetadata::new("1.2.0").unwrap()
13661478
} else {
13671479
semver::BuildMetadata::EMPTY
13681480
},
1369-
})
1481+
};
1482+
1483+
if let Some(v) = existing_version {
1484+
assert!(&new_version <= v, "{} <= {}", &new_version, v);
1485+
}
1486+
1487+
Ok(new_version)
1488+
}
1489+
1490+
fn gen_version(u: &mut Unstructured<'_>) -> Result<Version> {
1491+
gen_version_less_than(u, &None)
13701492
}

crates/wit-smith/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod generate;
1919
pub fn smith(config: &Config, u: &mut Unstructured<'_>) -> Result<Vec<u8>> {
2020
let pkgs = generate::Generator::new(config.clone()).generate(u)?;
2121
let mut resolve = Resolve::default();
22+
resolve.all_features = true;
2223
let mut last = None;
2324
for pkg in pkgs {
2425
let group = pkg.sources.parse().unwrap();

fuzz/src/roundtrip_wit.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ fn roundtrip_through_printing(file: &str, resolve: &Resolve, pkg: PackageId, was
126126
// Print to a single string, using nested `package ... { .. }` statements,
127127
// and then parse that in a new `Resolve`.
128128
let mut new_resolve = Resolve::default();
129+
new_resolve.all_features = true;
129130
let package_deps = resolve
130131
.packages
131132
.iter()

0 commit comments

Comments
 (0)