From 72a24ae92a3fc0dc66595fe6f72cfd55f065e15e Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Thu, 27 Feb 2025 23:00:20 +0000 Subject: [PATCH] fix mismatch in stability attributes error in wit-parser When a new interface is marked as @unstable (feature = somefeaturegate) and it uses a stable type from another package via use package:interface/type.{name} the resulting package should be able to be imported into other packages that use it. The use case for this is adding a unstable interface such as wasi-tls to wasi-cli. Once we get to resolve_include's processing we've already verified that everything checks out from a feature/version perspective for those features. i.e. we can't be trying to consolidate an interface type with a feature that hasn't already been resolved. Signed-off-by: James Sturtevant --- crates/wit-parser/src/lib.rs | 34 +- crates/wit-parser/src/resolve.rs | 25 +- .../ui/gated-include-use-with-stable.wit | 75 ++++ .../ui/gated-include-use-with-stable.wit.json | 397 ++++++++++++++++++ 4 files changed, 513 insertions(+), 18 deletions(-) create mode 100644 crates/wit-parser/tests/ui/gated-include-use-with-stable.wit create mode 100644 crates/wit-parser/tests/ui/gated-include-use-with-stable.wit.json diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index cc5ee29d4e..943ac16f91 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -1079,18 +1079,21 @@ fn find_futures_and_streams(resolve: &Resolve, ty: Type, results: &mut Vec, }, - /// `@unstable(feature = foo)` + /// `@since(version = 1.2.3)` /// - /// This item is explicitly tagged `@unstable`. A feature name is listed and - /// this item is excluded by default in `Resolve` unless explicitly enabled. - Unstable { - feature: String, + /// This item is explicitly tagged with `@since` as stable since the + /// specified version. This may optionally have a feature listed as well. + Stable { + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_version"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_version"))] + since: Version, #[cfg_attr( feature = "serde", serde( @@ -1120,9 +1125,6 @@ pub enum Stability { )] deprecated: Option, }, - - /// This item does not have either `@since` or `@unstable`. - Unknown, } impl Stability { diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 2f8de7c16d..8d715a286e 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -3414,7 +3414,7 @@ impl Remap { }, ) => { assert_eq!(*aid, *bid); - update_stability(astability, bstability)?; + merge_stability(astability, bstability)?; } (WorldItem::Interface { .. }, _) => unreachable!(), (WorldItem::Function(_), _) => unreachable!(), @@ -3833,7 +3833,28 @@ fn update_stability(from: &Stability, into: &mut Stability) -> Result<()> { // Failing all that this means that the two attributes are different so // generate an error. - bail!("mismatch in stability attributes") + bail!("mismatch in stability from '{:?}' to '{:?}'", from, into) +} + +/// Compares the two attributes and if the `from` is more stable than the `into` then +/// it will elevate the `into` to the same stability. +/// This should be used after its already been confirmed that the types are the same and +/// should be apart of the component because versions/features are enabled. +fn merge_stability(from: &Stability, into: &mut Stability) -> Result<()> { + // If the two stability annotations are equal then + // there's nothing to do here. + if from == into { + return Ok(()); + } + + // if the from is more stable elevate stability of into + if from > into { + *into = from.clone(); + return Ok(()); + } + + // otherwise `into`` already has higher stability + return Ok(()); } /// An error that can be returned during "world elaboration" during various diff --git a/crates/wit-parser/tests/ui/gated-include-use-with-stable.wit b/crates/wit-parser/tests/ui/gated-include-use-with-stable.wit new file mode 100644 index 0000000000..159ffb3987 --- /dev/null +++ b/crates/wit-parser/tests/ui/gated-include-use-with-stable.wit @@ -0,0 +1,75 @@ +package wasmtime:test; + +world test{ + @unstable(feature = active) + include wasi:unstable/imports@0.2.3; + include wasi:foo/imports@0.2.3; +} + +world test-ordered{ + include wasi:foo/imports@0.2.3; + @unstable(feature = active) + include wasi:unstable/imports@0.2.3; +} + +package wasi:unstable@0.2.3 { + @unstable(feature = active) + world imports { + @unstable(feature = active) + use wasi:dep2/stable@0.2.3.{stable-resource}; + @unstable(feature = active) + use wasi:dep-unversioned/unversioned.{unversioned-resource}; + @unstable(feature = active) + use wasi:dep-unstable/unstable.{unstable-resource}; + } +} + +package wasi:foo@0.2.3 { + @since(version = 0.2.0) + world imports { + @since(version = 0.2.0) + include wasi:dep2/imports@0.2.3; + include wasi:dep-unversioned/imports; + include wasi:dep-unstable/imports; + } +} + +package wasi:dep2@0.2.3 { + @since(version = 0.2.0) + world imports { + @since(version = 0.2.0) + import stable; + } + @since(version = 0.2.1) + interface stable { + resource stable-resource { + } + } +} + +package wasi:dep-unversioned{ + world imports { + import unversioned; + } + interface unversioned { + resource unversioned-resource { + + } + } +} + +package wasi:dep-unstable{ + @unstable(feature = active) + world imports { + @unstable(feature = active) + import unstable; + } + @unstable(feature = active) + interface unstable { + @unstable(feature = active) + resource unstable-resource { + + } + } +} + diff --git a/crates/wit-parser/tests/ui/gated-include-use-with-stable.wit.json b/crates/wit-parser/tests/ui/gated-include-use-with-stable.wit.json new file mode 100644 index 0000000000..471d6b4c72 --- /dev/null +++ b/crates/wit-parser/tests/ui/gated-include-use-with-stable.wit.json @@ -0,0 +1,397 @@ +{ + "worlds": [ + { + "name": "imports", + "imports": { + "interface-0": { + "interface": { + "id": 0, + "stability": { + "unstable": { + "feature": "active" + } + } + } + } + }, + "exports": {}, + "package": 0, + "stability": { + "unstable": { + "feature": "active" + } + } + }, + { + "name": "imports", + "imports": { + "interface-1": { + "interface": { + "id": 1 + } + } + }, + "exports": {}, + "package": 1 + }, + { + "name": "imports", + "imports": { + "interface-2": { + "interface": { + "id": 2, + "stability": { + "stable": { + "since": "0.2.0" + } + } + } + } + }, + "exports": {}, + "package": 2, + "stability": { + "stable": { + "since": "0.2.0" + } + } + }, + { + "name": "imports", + "imports": { + "interface-2": { + "interface": { + "id": 2, + "stability": { + "stable": { + "since": "0.2.0" + } + } + } + }, + "interface-1": { + "interface": { + "id": 1 + } + }, + "interface-0": { + "interface": { + "id": 0, + "stability": { + "unstable": { + "feature": "active" + } + } + } + } + }, + "exports": {}, + "package": 3, + "stability": { + "stable": { + "since": "0.2.0" + } + } + }, + { + "name": "imports", + "imports": { + "interface-2": { + "interface": { + "id": 2, + "stability": { + "unstable": { + "feature": "active" + } + } + } + }, + "stable-resource": { + "type": 3 + }, + "interface-1": { + "interface": { + "id": 1, + "stability": { + "unstable": { + "feature": "active" + } + } + } + }, + "unversioned-resource": { + "type": 4 + }, + "interface-0": { + "interface": { + "id": 0, + "stability": { + "unstable": { + "feature": "active" + } + } + } + }, + "unstable-resource": { + "type": 5 + } + }, + "exports": {}, + "package": 4, + "stability": { + "unstable": { + "feature": "active" + } + } + }, + { + "name": "test", + "imports": { + "interface-2": { + "interface": { + "id": 2, + "stability": { + "stable": { + "since": "0.2.0" + } + } + } + }, + "stable-resource": { + "type": 3 + }, + "interface-1": { + "interface": { + "id": 1, + "stability": { + "unstable": { + "feature": "active" + } + } + } + }, + "unversioned-resource": { + "type": 4 + }, + "interface-0": { + "interface": { + "id": 0, + "stability": { + "unstable": { + "feature": "active" + } + } + } + }, + "unstable-resource": { + "type": 5 + } + }, + "exports": {}, + "package": 5 + }, + { + "name": "test-ordered", + "imports": { + "interface-2": { + "interface": { + "id": 2, + "stability": { + "stable": { + "since": "0.2.0" + } + } + } + }, + "interface-1": { + "interface": { + "id": 1, + "stability": { + "unstable": { + "feature": "active" + } + } + } + }, + "interface-0": { + "interface": { + "id": 0, + "stability": { + "unstable": { + "feature": "active" + } + } + } + }, + "stable-resource": { + "type": 3 + }, + "unversioned-resource": { + "type": 4 + }, + "unstable-resource": { + "type": 5 + } + }, + "exports": {}, + "package": 5 + } + ], + "interfaces": [ + { + "name": "unstable", + "types": { + "unstable-resource": 0 + }, + "functions": {}, + "stability": { + "unstable": { + "feature": "active" + } + }, + "package": 0 + }, + { + "name": "unversioned", + "types": { + "unversioned-resource": 1 + }, + "functions": {}, + "package": 1 + }, + { + "name": "stable", + "types": { + "stable-resource": 2 + }, + "functions": {}, + "stability": { + "stable": { + "since": "0.2.1" + } + }, + "package": 2 + } + ], + "types": [ + { + "name": "unstable-resource", + "kind": "resource", + "owner": { + "interface": 0 + }, + "stability": { + "unstable": { + "feature": "active" + } + } + }, + { + "name": "unversioned-resource", + "kind": "resource", + "owner": { + "interface": 1 + } + }, + { + "name": "stable-resource", + "kind": "resource", + "owner": { + "interface": 2 + } + }, + { + "name": "stable-resource", + "kind": { + "type": 2 + }, + "owner": { + "world": 4 + }, + "stability": { + "unstable": { + "feature": "active" + } + } + }, + { + "name": "unversioned-resource", + "kind": { + "type": 1 + }, + "owner": { + "world": 4 + }, + "stability": { + "unstable": { + "feature": "active" + } + } + }, + { + "name": "unstable-resource", + "kind": { + "type": 0 + }, + "owner": { + "world": 4 + }, + "stability": { + "unstable": { + "feature": "active" + } + } + } + ], + "packages": [ + { + "name": "wasi:dep-unstable", + "interfaces": { + "unstable": 0 + }, + "worlds": { + "imports": 0 + } + }, + { + "name": "wasi:dep-unversioned", + "interfaces": { + "unversioned": 1 + }, + "worlds": { + "imports": 1 + } + }, + { + "name": "wasi:dep2@0.2.3", + "interfaces": { + "stable": 2 + }, + "worlds": { + "imports": 2 + } + }, + { + "name": "wasi:foo@0.2.3", + "interfaces": {}, + "worlds": { + "imports": 3 + } + }, + { + "name": "wasi:unstable@0.2.3", + "interfaces": {}, + "worlds": { + "imports": 4 + } + }, + { + "name": "wasmtime:test", + "interfaces": {}, + "worlds": { + "test": 5, + "test-ordered": 6 + } + } + ] +} \ No newline at end of file