diff --git a/CHANGELOG.md b/CHANGELOG.md index 90557777..383ba3eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - Removed remaining Go 1.22 support. +### Fixed + +- [#306](https://github.com/bytecodealliance/go-modules/issues/306): do not emit incompatible version feature gates when serializing WIT. For example, if a world with version `0.1.0` *includes* a world from another package with a `@since(version = 0.2.0)` feature gate, then strip that feature gate when serializing to WIT if the version is greater than the world’s package version. + + ## [v0.6.2] — 2025-03-16 ### Fixed diff --git a/testdata/issues/issue306.wit b/testdata/issues/issue306.wit index 46a4c6b0..d52e2b80 100644 --- a/testdata/issues/issue306.wit +++ b/testdata/issues/issue306.wit @@ -1,12 +1,13 @@ // https://github.com/bytecodealliance/go-modules/issues/306 -package issues:issue306; +package issues:issue306@0.1.0; world w { - include issues:dep1/w; + include issues:dep1/w@0.2.1; } -package issues:dep1 { +package issues:dep1@0.2.1 { world w { + @since(version = 0.2.0) import issues:dep2/gated-interface; } } diff --git a/testdata/issues/issue306.wit.json b/testdata/issues/issue306.wit.json index 0fac3944..b0b42f62 100644 --- a/testdata/issues/issue306.wit.json +++ b/testdata/issues/issue306.wit.json @@ -5,7 +5,12 @@ "imports": { "interface-0": { "interface": { - "id": 0 + "id": 0, + "stability": { + "stable": { + "since": "0.2.0" + } + } } } }, @@ -17,7 +22,12 @@ "imports": { "interface-0": { "interface": { - "id": 0 + "id": 0, + "stability": { + "stable": { + "since": "0.2.0" + } + } } } }, @@ -60,14 +70,14 @@ "worlds": {} }, { - "name": "issues:dep1", + "name": "issues:dep1@0.2.1", "interfaces": {}, "worlds": { "w": 0 } }, { - "name": "issues:issue306", + "name": "issues:issue306@0.1.0", "docs": { "contents": "https://github.com/bytecodealliance/go-modules/issues/306" }, diff --git a/testdata/issues/issue306.wit.json.golden.wit b/testdata/issues/issue306.wit.json.golden.wit index ac07c003..cdef08fb 100644 --- a/testdata/issues/issue306.wit.json.golden.wit +++ b/testdata/issues/issue306.wit.json.golden.wit @@ -1,11 +1,12 @@ -package issues:dep1; +package issues:dep1@0.2.1; world w { + @since(version = 0.2.0) import issues:dep2/gated-interface; } /// https://github.com/bytecodealliance/go-modules/issues/306 -package issues:issue306 { +package issues:issue306@0.1.0 { world w { import issues:dep2/gated-interface; } diff --git a/wit/bindgen/generator.go b/wit/bindgen/generator.go index d1d16ad1..d9794922 100644 --- a/wit/bindgen/generator.go +++ b/wit/bindgen/generator.go @@ -29,6 +29,7 @@ import ( "go.bytecodealliance.org/internal/wasmtools" "go.bytecodealliance.org/wit" "go.bytecodealliance.org/wit/logging" + "go.bytecodealliance.org/wit/ordered" ) const ( @@ -166,7 +167,7 @@ func newGenerator(res *wit.Resolve, opts ...Option) (*generator, error) { if g.world.Match(g.opts.world) { break } - // otherwise chose the last world + // otherwise choose the last world } g.wasmTools, err = wasmtools.New(context.Background()) if err != nil { @@ -2439,6 +2440,8 @@ func synthesizeWorld(r *wit.Resolve, w *wit.World, name string) (*wit.Resolve, * w = w.Clone() w.Name = name w.Docs = wit.Docs{} + // stripStability(w.Imports) + // stripStability(w.Exports) w.Package.Worlds.Set(name, w) r = r.Clone() @@ -2446,3 +2449,16 @@ func synthesizeWorld(r *wit.Resolve, w *wit.World, name string) (*wit.Resolve, * return r, w } + +// Strip stability from world items for serialization. +// https://github.com/bytecodealliance/go-modules/issues/306#issuecomment-2737873621 +func stripStability(items ordered.Map[string, wit.WorldItem]) { + items.All()(func(name string, i wit.WorldItem) bool { + if ref, ok := i.(*wit.InterfaceRef); ok { + ref2 := *ref + ref2.Stability = nil + items.Set(name, &ref2) + } + return true + }) +} diff --git a/wit/wit.go b/wit/wit.go index 61170c45..202621fc 100644 --- a/wit/wit.go +++ b/wit/wit.go @@ -116,6 +116,14 @@ func unwrap(s string) string { return line } +// newline returns either a blank string or a string with a newline. +func newline(s string) string { + if s == "" { + return "" + } + return s + "\n" +} + // WITKind returns the WIT kind. func (*Resolve) WITKind() string { return "resolve" } @@ -156,8 +164,23 @@ func (*Stable) WITKind() string { return "@since" } // WIT returns the [WIT] text format for [Stable] s. // // [WIT]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md -func (s *Stable) WIT(_ Node, _ string) string { +func (s *Stable) WIT(ctx Node, _ string) string { var b strings.Builder + var w *World + switch ctx := ctx.(type) { + case worldImport: + w = ctx.World + case worldExport: + w = ctx.World + case *World: + w = ctx + } + if w != nil && w.Package.Name.Version != nil { + pv := w.Package.Name.Version + if pv.LessThan(s.Since) { + return "" + } + } b.WriteString("@since(version = ") b.WriteString(s.Since.String()) b.WriteRune(')') @@ -256,8 +279,7 @@ func (w *World) WIT(ctx Node, name string) string { var b strings.Builder b.WriteString(w.Docs.WIT(nil, "")) if w.Stability != nil { - b.WriteString(w.Stability.WIT(nil, "")) - b.WriteRune('\n') + b.WriteString(newline(w.Stability.WIT(w, ""))) } b.WriteString("world ") b.WriteString(escape(name)) // TODO: compare to w.Name? @@ -311,7 +333,7 @@ func (ref *InterfaceRef) WIT(ctx Node, name string) string { if ref.Stability == nil { return ref.Interface.WIT(ctx, name) } - return ref.Stability.WIT(ctx, "") + "\n" + ref.Interface.WIT(ctx, name) + return newline(ref.Stability.WIT(ctx, "")) + ref.Interface.WIT(ctx, name) } // WITKind returns the WIT kind. @@ -357,8 +379,7 @@ func (i *Interface) WIT(ctx Node, name string) string { default: // e.g. *Package b.WriteString(i.Docs.WIT(ctx, "")) if i.Stability != nil { - b.WriteString(i.Stability.WIT(ctx, "")) - b.WriteRune('\n') + b.WriteString(newline(i.Stability.WIT(ctx, ""))) } b.WriteString("interface ") b.WriteString(escape(name)) @@ -455,8 +476,7 @@ func (t *TypeDef) WIT(ctx Node, name string) string { var b strings.Builder b.WriteString(t.Docs.WIT(ctx, "")) if t.Stability != nil { - b.WriteString(t.Stability.WIT(ctx, "")) - b.WriteRune('\n') + b.WriteString(newline(t.Stability.WIT(ctx, ""))) } b.WriteString(t.Kind.WIT(t, name)) constructor := t.Constructor() @@ -1051,8 +1071,7 @@ func (f *Function) WIT(ctx Node, name string) string { if ctx != nil { b.WriteString(f.Docs.WIT(ctx, "")) if f.Stability != nil { - b.WriteString(f.Stability.WIT(ctx, "")) - b.WriteRune('\n') + b.WriteString(newline(f.Stability.WIT(ctx, ""))) } } switch ctx.(type) {