From c79d7eaf870e0f562e49e1bbc3cba1e6c73b3b4b Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 11 Sep 2026 15:36:43 -0600 Subject: [PATCH] don't combine stream/future ctors so aggressively Previously, we would only emit one such constructor function per future or stream "canonical" payload type, where canonicalization would combine structurally equal (but not necessarily nominally equal) types. Since we encode the name of the payload type in the name of the constructor function, that made it hard to predict which names would be generated. Now we deduplicate based on the mangled name instead. --- src/summary.rs | 35 ++++++++++------------------------- src/test/python_source/app.py | 9 +++++++++ src/test/tests.rs | 27 +++++++++++++++++++++++++++ src/test/wit/tests.wit | 13 +++++++++++++ 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/summary.rs b/src/summary.rs index c13746b..da8f50f 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -2019,11 +2019,13 @@ class {camel}(Protocol): | TypeDefKind::Result(_) | TypeDefKind::Handle(_) => (None, Vec::new()), TypeDefKind::Stream(ty) => { - let canonical = canonical_payload(self.resolve, &mut types, ty); - let code = if stream_payloads.contains(&canonical) { + let snake = ty + .map(|ty| names.mangle_name(ty)) + .unwrap_or_else(|| "unit".into()); + let code = if stream_payloads.contains(&snake) { None } else { - stream_payloads.insert(canonical); + stream_payloads.insert(snake.clone()); Some(if let Some(Type::U8 | Type::S8) = ty { if stub_runtime_calls { @@ -2044,9 +2046,6 @@ def byte_stream() -> tuple[ByteStreamWriter, ByteStreamReader]: ) } } else { - let snake = ty - .map(|ty| names.mangle_name(ty)) - .unwrap_or_else(|| "unit".into()); let camel = ty .map(|ty| names.type_name(ty, &seen, None)) .unwrap_or_else(|| "None".into()); @@ -2073,15 +2072,14 @@ def {snake}_stream() -> tuple[StreamWriter[{camel}], StreamReader[{camel}]]: (code.map(Code::Shared), Vec::new()) } TypeDefKind::Future(ty) => { - let canonical = canonical_payload(self.resolve, &mut types, ty); - let code = if future_payloads.contains(&canonical) { + let snake = ty + .map(|ty| names.mangle_name(ty)) + .unwrap_or_else(|| "unit".into()); + let code = if future_payloads.contains(&snake) { None } else { - future_payloads.insert(canonical); + future_payloads.insert(snake.clone()); - let snake = ty - .map(|ty| names.mangle_name(ty)) - .unwrap_or_else(|| "unit".into()); let camel = ty .map(|ty| names.type_name(ty, &seen, None)) .unwrap_or_else(|| "None".into()); @@ -3260,19 +3258,6 @@ fn docstring(docs: Option<&str>, indent_level: usize, error: Option<&str>) -> St } } -fn canonical_payload(resolve: &Resolve, types: &mut Types, ty: &Option) -> Option { - match ty { - Some(Type::Id(id)) => { - let id = types.get_representative_type(*id); - match resolve.types[id].kind { - TypeDefKind::Type(t) => Some(t), - _ => Some(Type::Id(id)), - } - } - other => *other, - } -} - fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId { loop { match &resolve.types[id].kind { diff --git a/src/test/python_source/app.py b/src/test/python_source/app.py index 1785503..6d4a74f 100644 --- a/src/test/python_source/app.py +++ b/src/test/python_source/app.py @@ -19,6 +19,7 @@ from tests.imports.componentize_py.test import host_thing_interface from tests.exports.componentize_py.test import resource_alias2 from tests.exports.componentize_py.test import streams_and_futures +from tests.exports.componentize_py.test import similar_streams_and_futures from typing import Tuple, List, Optional from foo_sdk.wit.exports.foo import sdk as foo_exports from foo_sdk.wit.exports.foo.sdk import foo_interface as foo_iface @@ -297,3 +298,11 @@ def test(self, s: str) -> str: class BarInterface(bar_exports.BarInterface): def test(self, s: str) -> str: return bar_test(f"{s} BarInterface.test") + +@exports.similar_streams_and_futures.guest +class SimilarStreamsAndFutures(exports.SimilarStreamsAndFutures): + async def baz(self) -> tuple[StreamReader[similar_streams_and_futures.Foo], StreamReader[similar_streams_and_futures.Bar], FutureReader[similar_streams_and_futures.Foo], FutureReader[similar_streams_and_futures.Bar]]: + return (tests.componentize_py_test_similar_streams_and_futures_foo_stream()[1], + tests.componentize_py_test_similar_streams_and_futures_bar_stream()[1], + tests.componentize_py_test_similar_streams_and_futures_foo_future(lambda: similar_streams_and_futures.Foo_A())[1], + tests.componentize_py_test_similar_streams_and_futures_bar_future(lambda: similar_streams_and_futures.Bar_A())[1]) diff --git a/src/test/tests.rs b/src/test/tests.rs index 0392abc..fe90903 100644 --- a/src/test/tests.rs +++ b/src/test/tests.rs @@ -1400,6 +1400,33 @@ fn test_echo_stream_u8(delay: bool) -> Result<()> { }) } +#[test] +fn test_similar_streams_and_futures() -> Result<()> { + // Previously, `componentize-py` would coalesce stream and future + // constructor functions whose payloads were structurally (but not + // nominally) equivalent, making the names unpredictable. This test ensures + // we don't do that anymore. + TESTER.test(|world, store, runtime| { + runtime.block_on(async { + store + .run_concurrent(async |store| { + // Just call the function and make sure the stream and + // future constructor functions are resolved in the Python + // code: + world + .componentize_py_test_similar_streams_and_futures() + .call_baz(store) + .await?; + + anyhow::Ok(()) + }) + .await? + })?; + + Ok(()) + }) +} + struct OptionProducer { source: Option, sleep: Pin + Send>>, diff --git a/src/test/wit/tests.wit b/src/test/wit/tests.wit index afce618..72ccabb 100644 --- a/src/test/wit/tests.wit +++ b/src/test/wit/tests.wit @@ -165,6 +165,18 @@ interface streams-and-futures { dropped-future-reader-host: async func(value: string) -> tuple, future>; } +interface similar-streams-and-futures { + variant foo { + a, b, c + } + + variant bar { + a, b, c + } + + baz: async func() -> tuple, stream, future, future>; +} + world tests { use resource-alias1.{thing}; use resource-floats.{float}; @@ -190,6 +202,7 @@ world tests { import resource-borrow-in-record; export resource-borrow-in-record; export streams-and-futures; + export similar-streams-and-futures; export resource-floats-exports: interface { resource float {