diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index cc5b084cba..6255ed6b60 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -2932,6 +2932,7 @@ impl<'a> Shims<'a> { result: wit_result, docs: Default::default(), stability: Stability::Unknown, + span: None, }, if async_ { AbiVariant::GuestImportAsync @@ -3029,6 +3030,7 @@ fn task_return_options_and_type( result: None, docs: Default::default(), stability: Stability::Unknown, + span: None, }; let abi = AbiVariant::GuestImport; let options = RequiredOptions::for_import(resolve, &func_tmp, abi); diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 029205bcd9..fcf02caf27 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -92,6 +92,7 @@ impl Default for Bindgen { include_names: Default::default(), package: Some(package), stability: Default::default(), + span: None, }); resolve.packages[package] .worlds diff --git a/crates/wit-dylib/tests/roundtrip.rs b/crates/wit-dylib/tests/roundtrip.rs index ef60ea4e6d..c0748fdc85 100644 --- a/crates/wit-dylib/tests/roundtrip.rs +++ b/crates/wit-dylib/tests/roundtrip.rs @@ -133,6 +133,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { result: Some(Type::U32), stability: Default::default(), docs: Default::default(), + span: None, }, ); funcs.insert( @@ -144,6 +145,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { result: None, stability: Default::default(), docs: Default::default(), + span: None, }, ); funcs.insert( @@ -155,10 +157,12 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { result: Some(Type::U32), stability: Default::default(), docs: Default::default(), + span: None, }, ); funcs }, + span: None, }); // Generate two worlds in our custom package, one for the callee and one for @@ -173,6 +177,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { includes: Default::default(), include_names: Default::default(), docs: Default::default(), + span: None, }); let caller = resolve.worlds.alloc(World { name: "caller".to_string(), @@ -183,6 +188,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { includes: Default::default(), include_names: Default::default(), docs: Default::default(), + span: None, }); // Add an extra import/export for our synthesized interfaces as well. @@ -214,6 +220,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { result: None, stability: Default::default(), docs: Default::default(), + span: None, }), ); @@ -316,6 +323,7 @@ fn update_resources(resolve: &mut Resolve) { owner: TypeOwner::None, docs: Default::default(), stability: Default::default(), + span: None, }); let borrow = resolve.types.alloc(TypeDef { name: None, @@ -323,6 +331,7 @@ fn update_resources(resolve: &mut Resolve) { owner: TypeOwner::None, docs: Default::default(), stability: Default::default(), + span: None, }); let iface = &mut resolve.interfaces[interface_id]; let ctor = format!("[constructor]{resource_name}"); @@ -339,6 +348,7 @@ fn update_resources(resolve: &mut Resolve) { result: Some(Type::Id(own)), stability: Default::default(), docs: Default::default(), + span: None, }, ); iface.functions.insert( @@ -350,6 +360,7 @@ fn update_resources(resolve: &mut Resolve) { result: Some(Type::U32), stability: Default::default(), docs: Default::default(), + span: None, }, ); } diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index a2253877e3..e50e445f64 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1689,13 +1689,13 @@ fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> Result { /// [`UnresolvedPackage`]. /// /// [`UnresolvedPackage`]: crate::UnresolvedPackage -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct SourceMap { sources: Vec, offset: u32, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Source { offset: u32, path: String, @@ -1754,6 +1754,21 @@ impl SourceMap { self.offset = new_offset; } + /// Appends all sources from another `SourceMap` into this one. + /// + /// Returns the byte offset that should be added to all `Span.start` and + /// `Span.end` values from the appended source map to make them valid + /// in the combined source map. + pub fn append(&mut self, other: SourceMap) -> u32 { + let base = self.offset; + for mut source in other.sources { + source.offset += base; + self.sources.push(source); + } + self.offset += other.offset; + base + } + /// Parses the files added to this source map into a /// [`UnresolvedPackageGroup`]. pub fn parse(self) -> Result { @@ -1900,7 +1915,8 @@ impl SourceMap { return msg; } - pub(crate) fn render_location(&self, span: Span) -> String { + /// Renders a span as a human-readable location string (e.g., "file.wit:10:5"). + pub fn render_location(&self, span: Span) -> String { let src = self.source_for_offset(span.start); let start = src.to_relative_offset(span.start); let (line, col) = src.linecol(start); diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index 4ee653ced3..58a339164c 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -21,7 +21,7 @@ struct CrlfFold<'a> { } /// A span, designating a range of bytes where a token is located. -#[derive(Eq, PartialEq, Debug, Clone, Copy)] +#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] pub struct Span { /// The start of the range. pub start: u32, @@ -29,6 +29,14 @@ pub struct Span { pub end: u32, } +impl Span { + /// Adjusts this span by adding the given byte offset to both start and end. + pub fn adjust(&mut self, offset: u32) { + self.start += offset; + self.end += offset; + } +} + #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub enum Token { Whitespace, diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 43343d1582..40b7235b11 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -329,6 +329,7 @@ impl<'a> Resolver<'a> { stability: Default::default(), functions: IndexMap::default(), package: None, + span: Some(span), }) } @@ -348,6 +349,7 @@ impl<'a> Resolver<'a> { includes: Default::default(), include_names: Default::default(), stability: Default::default(), + span: Some(span), }) } @@ -599,6 +601,7 @@ impl<'a> Resolver<'a> { kind: TypeDefKind::Unknown, name: Some(name.name.name.to_string()), owner: TypeOwner::Interface(iface), + span: Some(name.name.span), }); self.unknown_type_spans.push(name.name.span); self.type_spans.push(name.name.span); @@ -951,6 +954,7 @@ impl<'a> Resolver<'a> { kind, name: Some(def.name.name.to_string()), owner, + span: Some(def.name.span), }); self.type_spans.push(def.name.span); self.define_interface_name(&def.name, TypeOrItem::Type(id))?; @@ -999,6 +1003,7 @@ impl<'a> Resolver<'a> { )), }; self.type_spans.push(name.name.span); + let span = name.name.span; let name = name.as_.as_ref().unwrap_or(&name.name); let id = self.types.alloc(TypeDef { docs: Docs::default(), @@ -1006,6 +1011,7 @@ impl<'a> Resolver<'a> { kind: TypeDefKind::Type(Type::Id(id)), name: Some(name.name.to_string()), owner, + span: Some(span), }); self.define_interface_name(name, TypeOrItem::Type(id))?; } @@ -1096,6 +1102,7 @@ impl<'a> Resolver<'a> { kind, params, result, + span: Some(func.span), }) } @@ -1263,6 +1270,7 @@ impl<'a> Resolver<'a> { docs: self.docs(&field.docs), name: field.name.name.to_string(), ty: self.resolve_type(&field.ty, stability)?, + span: Some(field.name.span), }) }) .collect::>>()?; @@ -1275,6 +1283,7 @@ impl<'a> Resolver<'a> { .map(|flag| Flag { docs: self.docs(&flag.docs), name: flag.name.name.to_string(), + span: Some(flag.name.span), }) .collect::>(); TypeDefKind::Flags(Flags { flags }) @@ -1299,6 +1308,7 @@ impl<'a> Resolver<'a> { docs: self.docs(&case.docs), name: case.name.name.to_string(), ty: self.resolve_optional_type(case.ty.as_ref(), stability)?, + span: Some(case.name.span), }) }) .collect::>>()?; @@ -1315,6 +1325,7 @@ impl<'a> Resolver<'a> { Ok(EnumCase { docs: self.docs(&case.docs), name: case.name.name.to_string(), + span: Some(case.name.span), }) }) .collect::>>()?; @@ -1453,6 +1464,7 @@ impl<'a> Resolver<'a> { docs: Docs::default(), stability, owner: TypeOwner::None, + span: Some(ty.span()), }, ty.span(), )) @@ -1654,6 +1666,7 @@ impl<'a> Resolver<'a> { kind, name: None, owner: TypeOwner::None, + span: Some(span), }, span, ); diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 40bebd3455..b9023e76a5 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -311,6 +311,7 @@ impl ComponentInfo { includes: Default::default(), include_names: Default::default(), stability: Default::default(), + span: None, }); let mut package = Package { // Similar to `world_name` above this is arbitrarily chosen as it's @@ -900,6 +901,7 @@ impl WitPackageDecoder<'_> { functions: IndexMap::default(), package: None, stability: Default::default(), + span: None, }) }); @@ -955,6 +957,7 @@ impl WitPackageDecoder<'_> { functions: IndexMap::default(), package: None, stability: Default::default(), + span: None, }; let owner = TypeOwner::Interface(self.resolve.interfaces.next_id()); @@ -1051,6 +1054,7 @@ impl WitPackageDecoder<'_> { docs: Default::default(), stability: Default::default(), owner, + span: None, }); // If this is a resource then doubly-register it in `self.resources` so @@ -1088,6 +1092,7 @@ impl WitPackageDecoder<'_> { include_names: Default::default(), package: None, stability: Default::default(), + span: None, }; let owner = TypeOwner::World(self.resolve.worlds.next_id()); @@ -1238,6 +1243,7 @@ impl WitPackageDecoder<'_> { name: name.to_string(), params, result, + span: None, }) } @@ -1287,6 +1293,7 @@ impl WitPackageDecoder<'_> { stability: Default::default(), owner: TypeOwner::None, kind, + span: None, }); let prev = self.type_map.insert(id.into(), ty); assert!(prev.is_none()); @@ -1353,6 +1360,7 @@ impl WitPackageDecoder<'_> { format!("failed to convert record field '{name}'") })?, docs: Default::default(), + span: None, }) }) .collect::>()?; @@ -1371,6 +1379,7 @@ impl WitPackageDecoder<'_> { None => None, }, docs: Default::default(), + span: None, }) }) .collect::>()?; @@ -1383,6 +1392,7 @@ impl WitPackageDecoder<'_> { .map(|name| Flag { name: name.to_string(), docs: Default::default(), + span: None, }) .collect(); Ok(TypeDefKind::Flags(Flags { flags })) @@ -1395,6 +1405,7 @@ impl WitPackageDecoder<'_> { .map(|name| EnumCase { name: name.into(), docs: Default::default(), + span: None, }) .collect(); Ok(TypeDefKind::Enum(Enum { cases })) diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 25217a7423..0822d1934d 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -46,7 +46,7 @@ pub use metadata::PackageMetadata; pub mod abi; mod ast; pub use ast::SourceMap; -use ast::lex::Span; +pub use ast::lex::Span; pub use ast::{ParsedUsePath, parse_use_path}; mod sizealign; pub use sizealign::*; @@ -146,6 +146,58 @@ pub struct UnresolvedPackage { required_resource_types: Vec<(TypeId, Span)>, } +impl UnresolvedPackage { + /// Adjusts all spans in this package by adding the given byte offset. + /// + /// This is used when merging source maps to update spans to point to the + /// correct location in the combined source map. + pub(crate) fn adjust_spans(&mut self, offset: u32) { + // Adjust parallel vec spans + self.package_name_span.adjust(offset); + for span in &mut self.unknown_type_spans { + span.adjust(offset); + } + for ispan in &mut self.interface_spans { + ispan.span.adjust(offset); + for span in &mut ispan.funcs { + span.adjust(offset); + } + } + for wspan in &mut self.world_spans { + wspan.span.adjust(offset); + for span in &mut wspan.imports { + span.adjust(offset); + } + for span in &mut wspan.exports { + span.adjust(offset); + } + for span in &mut wspan.includes { + span.adjust(offset); + } + } + for span in &mut self.type_spans { + span.adjust(offset); + } + for span in &mut self.foreign_dep_spans { + span.adjust(offset); + } + for (_, span) in &mut self.required_resource_types { + span.adjust(offset); + } + + // Adjust spans on arena items + for (_, world) in self.worlds.iter_mut() { + world.adjust_spans(offset); + } + for (_, iface) in self.interfaces.iter_mut() { + iface.adjust_spans(offset); + } + for (_, ty) in self.types.iter_mut() { + ty.adjust_spans(offset); + } + } +} + /// Tracks a set of packages, all pulled from the same group of WIT source files. #[derive(Clone)] pub struct UnresolvedPackageGroup { @@ -473,6 +525,24 @@ pub struct World { /// All the included worlds names. Empty if this is fully resolved #[cfg_attr(feature = "serde", serde(skip))] pub include_names: Vec>, + + /// Source span for this world, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, +} + +impl World { + /// Adjusts all spans in this world by adding the given byte offset. + pub(crate) fn adjust_spans(&mut self, offset: u32) { + if let Some(s) = &mut self.span { + s.adjust(offset); + } + for item in self.imports.values_mut().chain(self.exports.values_mut()) { + if let WorldItem::Function(f) = item { + f.adjust_spans(offset); + } + } + } } #[derive(Debug, Clone)] @@ -610,6 +680,22 @@ pub struct Interface { /// The package that owns this interface. #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_optional_id"))] pub package: Option, + + /// Source span for this interface, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, +} + +impl Interface { + /// Adjusts all spans in this interface by adding the given byte offset. + pub(crate) fn adjust_spans(&mut self, offset: u32) { + if let Some(s) = &mut self.span { + s.adjust(offset); + } + for func in self.functions.values_mut() { + func.adjust_spans(offset); + } + } } #[derive(Debug, Clone, PartialEq)] @@ -626,6 +712,52 @@ pub struct TypeDef { serde(skip_serializing_if = "Stability::is_unknown") )] pub stability: Stability, + /// Source span for this type, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, +} + +impl TypeDef { + /// Adjusts all spans in this type definition by adding the given byte offset. + /// + /// This is used when merging source maps to update spans to point to the + /// correct location in the combined source map. + pub(crate) fn adjust_spans(&mut self, offset: u32) { + if let Some(s) = &mut self.span { + s.adjust(offset); + } + match &mut self.kind { + TypeDefKind::Record(r) => { + for field in &mut r.fields { + if let Some(s) = &mut field.span { + s.adjust(offset); + } + } + } + TypeDefKind::Variant(v) => { + for case in &mut v.cases { + if let Some(s) = &mut case.span { + s.adjust(offset); + } + } + } + TypeDefKind::Enum(e) => { + for case in &mut e.cases { + if let Some(s) = &mut case.span { + s.adjust(offset); + } + } + } + TypeDefKind::Flags(f) => { + for flag in &mut f.flags { + if let Some(s) = &mut flag.span { + s.adjust(offset); + } + } + } + _ => {} + } + } } #[derive(Debug, Clone, PartialEq, Hash, Eq)] @@ -749,6 +881,9 @@ pub struct Field { pub ty: Type, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Source span for this field, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, } #[derive(Debug, Clone, PartialEq, Hash, Eq)] @@ -763,6 +898,9 @@ pub struct Flag { pub name: String, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Source span for this flag, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, } #[derive(Debug, Clone, PartialEq)] @@ -813,6 +951,9 @@ pub struct Case { pub ty: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Source span for this variant case, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, } impl Variant { @@ -833,6 +974,9 @@ pub struct EnumCase { pub name: String, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Source span for this enum case, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, } impl Enum { @@ -888,6 +1032,10 @@ pub struct Function { serde(skip_serializing_if = "Stability::is_unknown") )] pub stability: Stability, + + /// Source span for this function, if parsed from WIT text. + #[cfg_attr(feature = "serde", serde(skip))] + pub span: Option, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1151,6 +1299,13 @@ impl ManglingAndAbi { } impl Function { + /// Adjusts all spans in this function by adding the given byte offset. + pub(crate) fn adjust_spans(&mut self, offset: u32) { + if let Some(s) = &mut self.span { + s.adjust(offset); + } + } + pub fn item_name(&self) -> &str { match &self.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &self.name, @@ -1428,6 +1583,7 @@ mod test { owner: TypeOwner::None, docs: Docs::default(), stability: Stability::Unknown, + span: None, }); let t1 = resolve.types.alloc(TypeDef { name: None, @@ -1435,6 +1591,7 @@ mod test { owner: TypeOwner::None, docs: Docs::default(), stability: Stability::Unknown, + span: None, }); let t2 = resolve.types.alloc(TypeDef { name: None, @@ -1442,6 +1599,7 @@ mod test { owner: TypeOwner::None, docs: Docs::default(), stability: Stability::Unknown, + span: None, }); let found = Function { name: "foo".into(), @@ -1450,6 +1608,7 @@ mod test { result: Some(Type::Id(t2)), docs: Docs::default(), stability: Stability::Unknown, + span: None, } .find_futures_and_streams(&resolve); assert_eq!(3, found.len()); diff --git a/crates/wit-parser/src/resolve/mod.rs b/crates/wit-parser/src/resolve/mod.rs index 8d8eb30e29..a4f63a1e04 100644 --- a/crates/wit-parser/src/resolve/mod.rs +++ b/crates/wit-parser/src/resolve/mod.rs @@ -99,6 +99,10 @@ pub struct Resolve { /// Activate all features for this [`Resolve`]. #[cfg_attr(feature = "serde", serde(skip))] pub all_features: bool, + + /// Source map for converting spans to file locations. + #[cfg_attr(feature = "serde", serde(skip))] + pub source_map: SourceMap, } /// A WIT package within a `Resolve`. @@ -307,16 +311,22 @@ package {name} is defined in two different locations:\n\ } } - // Ensure that the final output is topologically sorted. Use a set to ensure that we render - // the buffers for each `SourceMap` only once, even though multiple packages may reference - // the same `SourceMap`. + // Ensure that the final output is topologically sorted. Track which source maps + // have been appended and their byte offsets to avoid duplicating them. let mut package_id_to_source_map_idx = BTreeMap::new(); let mut main_pkg_id = None; + let mut source_map_offsets: HashMap = HashMap::new(); for name in order { let (pkg, source_map_index) = pkg_details_map.remove(&name).unwrap(); let source_map = &source_maps[source_map_index]; let is_main = pkg.name == main_name; - let id = self.push(pkg, source_map)?; + + // Get or compute the span offset for this source map + let span_offset = *source_map_offsets + .entry(source_map_index) + .or_insert_with(|| self.push_source_map(source_map.clone())); + + let id = self.push(pkg, span_offset)?; if is_main { assert!(main_pkg_id.is_none()); main_pkg_id = Some(id); @@ -330,6 +340,16 @@ package {name} is defined in two different locations:\n\ )) } + /// Appends a source map to this [`Resolve`]'s internal source map. + /// + /// Returns the byte offset that should be passed to [`Resolve::push`] for + /// packages parsed from this source map. This offset ensures that spans + /// in the resolved package point to the correct location in the combined + /// source map. + pub fn push_source_map(&mut self, source_map: SourceMap) -> u32 { + self.source_map.append(source_map) + } + /// Appends a new [`UnresolvedPackage`] to this [`Resolve`], creating a /// fully resolved package with no dangling references. /// @@ -337,20 +357,25 @@ package {name} is defined in two different locations:\n\ /// within this `Resolve` via previous calls to `push` or other methods such /// as [`Resolve::push_path`]. /// + /// The `span_offset` should be the value returned by + /// [`Resolve::push_source_map`] if the source map was appended to this + /// resolve, or `0` if this is a standalone package. + /// /// Any dependency resolution error or otherwise world-elaboration error /// will be returned here, if successful a package identifier is returned /// which corresponds to the package that was just inserted. pub fn push( &mut self, - unresolved: UnresolvedPackage, - source_map: &SourceMap, + mut unresolved: UnresolvedPackage, + span_offset: u32, ) -> Result { - let ret = source_map.rewrite_error(|| Remap::default().append(self, unresolved)); + unresolved.adjust_spans(span_offset); + let ret = Remap::default().append(self, unresolved); if ret.is_ok() { #[cfg(debug_assertions)] self.assert_valid(); } - ret + self.source_map.rewrite_error(|| ret) } /// Appends new [`UnresolvedPackageGroup`] to this [`Resolve`], creating a @@ -376,6 +401,11 @@ package {name} is defined in two different locations:\n\ self.push_group(UnresolvedPackageGroup::parse_str(path, contents)?) } + /// Renders a span as a human-readable location string (e.g., "file.wit:10:5"). + pub fn render_location(&self, span: Span) -> String { + self.source_map.render_location(span) + } + pub fn all_bits_valid(&self, ty: &Type) -> bool { match ty { Type::U8 @@ -479,9 +509,12 @@ package {name} is defined in two different locations:\n\ packages, package_names, features: _, + source_map, .. } = resolve; + let span_offset = self.source_map.append(source_map); + let mut moved_types = Vec::new(); for (id, mut ty) in types { let new_id = match type_map.get(&id).copied() { @@ -493,6 +526,7 @@ package {name} is defined in two different locations:\n\ log::debug!("moving type {:?}", ty.name); moved_types.push(id); remap.update_typedef(self, &mut ty, None)?; + ty.adjust_spans(span_offset); self.types.alloc(ty) } }; @@ -511,6 +545,7 @@ package {name} is defined in two different locations:\n\ log::debug!("moving interface {:?}", iface.name); moved_interfaces.push(id); remap.update_interface(self, &mut iface, None)?; + iface.adjust_spans(span_offset); self.interfaces.alloc(iface) } }; @@ -558,6 +593,7 @@ package {name} is defined in two different locations:\n\ }; update(&mut world.imports)?; update(&mut world.exports)?; + world.adjust_spans(span_offset); self.worlds.alloc(world) } }; @@ -2714,6 +2750,7 @@ impl Remap { kind: TypeDefKind::Handle(Handle::Own(id)), docs: _, stability: _, + span: _, } => *self.own_handles.entry(id).or_insert(new_id), // Everything not-related to `own` doesn't get its ID @@ -3234,6 +3271,7 @@ impl Remap { kind: TypeDefKind::Handle(Handle::Own(*id)), docs: Default::default(), stability: Default::default(), + span: None, }) }); } @@ -4089,7 +4127,8 @@ impl core::error::Error for InvalidTransitiveDependency {} #[cfg(test)] mod tests { - use crate::Resolve; + use crate::alloc::string::ToString; + use crate::{Resolve, WorldItem, WorldKey}; use anyhow::Result; #[test] @@ -4459,4 +4498,529 @@ mod tests { Ok(()) } + + #[test] + fn span_preservation() -> Result<()> { + let mut resolve = Resolve::default(); + let pkg = resolve.push_str( + "test.wit", + r#" + package foo:bar; + + interface my-iface { + type my-type = u32; + my-func: func(); + } + + world my-world { + export my-export: func(); + } + "#, + )?; + + let iface_id = resolve.packages[pkg].interfaces["my-iface"]; + assert!(resolve.interfaces[iface_id].span.is_some()); + + let type_id = resolve.interfaces[iface_id].types["my-type"]; + assert!(resolve.types[type_id].span.is_some()); + + assert!( + resolve.interfaces[iface_id].functions["my-func"] + .span + .is_some() + ); + + let world_id = resolve.packages[pkg].worlds["my-world"]; + assert!(resolve.worlds[world_id].span.is_some()); + + let WorldItem::Function(f) = + &resolve.worlds[world_id].exports[&WorldKey::Name("my-export".to_string())] + else { + panic!("expected function"); + }; + assert!(f.span.is_some()); + + Ok(()) + } + + #[test] + fn span_preservation_through_merge() -> Result<()> { + let mut resolve1 = Resolve::default(); + resolve1.push_str( + "test1.wit", + r#" + package foo:bar; + + interface iface1 { + type type1 = u32; + func1: func(); + } + "#, + )?; + + let mut resolve2 = Resolve::default(); + let pkg2 = resolve2.push_str( + "test2.wit", + r#" + package foo:baz; + + interface iface2 { + type type2 = string; + func2: func(); + } + "#, + )?; + + let iface2_old_id = resolve2.packages[pkg2].interfaces["iface2"]; + let remap = resolve1.merge(resolve2)?; + let iface2_id = remap.interfaces[iface2_old_id.index()].unwrap(); + + assert!(resolve1.interfaces[iface2_id].span.is_some()); + + let type2_id = resolve1.interfaces[iface2_id].types["type2"]; + assert!(resolve1.types[type2_id].span.is_some()); + + assert!( + resolve1.interfaces[iface2_id].functions["func2"] + .span + .is_some() + ); + + Ok(()) + } + + #[test] + fn span_preservation_through_include() -> Result<()> { + let mut resolve = Resolve::default(); + let pkg = resolve.push_str( + "test.wit", + r#" + package foo:bar; + + world base { + export my-func: func(); + } + + world extended { + include base; + } + "#, + )?; + + let base_id = resolve.packages[pkg].worlds["base"]; + let extended_id = resolve.packages[pkg].worlds["extended"]; + + let WorldItem::Function(base_func) = + &resolve.worlds[base_id].exports[&WorldKey::Name("my-func".to_string())] + else { + panic!("expected function"); + }; + assert!(base_func.span.is_some()); + + let WorldItem::Function(extended_func) = + &resolve.worlds[extended_id].exports[&WorldKey::Name("my-func".to_string())] + else { + panic!("expected function"); + }; + assert!(extended_func.span.is_some()); + + Ok(()) + } + + #[test] + fn span_preservation_through_include_with_rename() -> Result<()> { + let mut resolve = Resolve::default(); + let pkg = resolve.push_str( + "test.wit", + r#" + package foo:bar; + + world base { + export original-name: func(); + } + + world extended { + include base with { original-name as renamed-func } + } + "#, + )?; + + let extended_id = resolve.packages[pkg].worlds["extended"]; + + let WorldItem::Function(f) = + &resolve.worlds[extended_id].exports[&WorldKey::Name("renamed-func".to_string())] + else { + panic!("expected function"); + }; + assert!(f.span.is_some()); + + assert!( + !resolve.worlds[extended_id] + .exports + .contains_key(&WorldKey::Name("original-name".to_string())) + ); + + Ok(()) + } + + /// Test that spans work when included world is defined after the including world + #[test] + fn span_preservation_through_include_reverse_order() -> Result<()> { + let mut resolve = Resolve::default(); + let pkg = resolve.push_str( + "test.wit", + r#" + package foo:bar; + + world extended { + include base; + } + + world base { + export my-func: func(); + } + "#, + )?; + + let base_id = resolve.packages[pkg].worlds["base"]; + let extended_id = resolve.packages[pkg].worlds["extended"]; + + let WorldItem::Function(base_func) = + &resolve.worlds[base_id].exports[&WorldKey::Name("my-func".to_string())] + else { + panic!("expected function"); + }; + assert!(base_func.span.is_some()); + + let WorldItem::Function(extended_func) = + &resolve.worlds[extended_id].exports[&WorldKey::Name("my-func".to_string())] + else { + panic!("expected function"); + }; + assert!(extended_func.span.is_some()); + + Ok(()) + } + + #[test] + fn span_line_numbers() -> Result<()> { + let mut resolve = Resolve::default(); + let pkg = resolve.push_source( + "test.wit", + "package foo:bar; + +interface my-iface { + type my-type = u32; + my-func: func(); +} + +world my-world { + export my-export: func(); +} +", + )?; + + let iface_id = resolve.packages[pkg].interfaces["my-iface"]; + let iface_span = resolve.interfaces[iface_id].span.unwrap(); + let iface_loc = resolve.render_location(iface_span); + assert!( + iface_loc.contains(":3:"), + "interface location was {iface_loc}" + ); + + let type_id = resolve.interfaces[iface_id].types["my-type"]; + let type_span = resolve.types[type_id].span.unwrap(); + let type_loc = resolve.render_location(type_span); + assert!(type_loc.contains(":4:"), "type location was {type_loc}"); + + let func_span = resolve.interfaces[iface_id].functions["my-func"] + .span + .unwrap(); + let func_loc = resolve.render_location(func_span); + assert!(func_loc.contains(":5:"), "function location was {func_loc}"); + + let world_id = resolve.packages[pkg].worlds["my-world"]; + let world_span = resolve.worlds[world_id].span.unwrap(); + let world_loc = resolve.render_location(world_span); + assert!(world_loc.contains(":8:"), "world location was {world_loc}"); + + let WorldItem::Function(export_func) = + &resolve.worlds[world_id].exports[&WorldKey::Name("my-export".to_string())] + else { + panic!("expected function"); + }; + let export_loc = resolve.render_location(export_func.span.unwrap()); + assert!( + export_loc.contains(":9:"), + "export location was {export_loc}" + ); + + Ok(()) + } + + #[test] + fn span_line_numbers_through_merge() -> Result<()> { + let mut resolve1 = Resolve::default(); + resolve1.push_source( + "first.wit", + "package foo:first; + +interface iface1 { + func1: func(); +} +", + )?; + + let mut resolve2 = Resolve::default(); + let pkg2 = resolve2.push_source( + "second.wit", + "package foo:second; + +interface iface2 { + func2: func(); +} +", + )?; + + let iface2_old_id = resolve2.packages[pkg2].interfaces["iface2"]; + let remap = resolve1.merge(resolve2)?; + let iface2_id = remap.interfaces[iface2_old_id.index()].unwrap(); + + let iface2_span = resolve1.interfaces[iface2_id].span.unwrap(); + let iface2_loc = resolve1.render_location(iface2_span); + assert!( + iface2_loc.contains("second.wit"), + "should reference second.wit, got {iface2_loc}" + ); + assert!( + iface2_loc.contains(":3:"), + "interface should be on line 3, got {iface2_loc}" + ); + + let func2_span = resolve1.interfaces[iface2_id].functions["func2"] + .span + .unwrap(); + let func2_loc = resolve1.render_location(func2_span); + assert!( + func2_loc.contains("second.wit"), + "should reference second.wit, got {func2_loc}" + ); + assert!( + func2_loc.contains(":4:"), + "function should be on line 4, got {func2_loc}" + ); + + Ok(()) + } + + #[test] + fn span_line_numbers_multiple_sources() -> Result<()> { + let mut resolve = Resolve::default(); + + let pkg1 = resolve.push_source( + "first.wit", + "package test:first; + +interface first-iface { + first-func: func(); +} +", + )?; + + let pkg2 = resolve.push_source( + "second.wit", + "package test:second; + +interface second-iface { + second-func: func(); +} +", + )?; + + let iface1_id = resolve.packages[pkg1].interfaces["first-iface"]; + let iface1_span = resolve.interfaces[iface1_id].span.unwrap(); + let iface1_loc = resolve.render_location(iface1_span); + assert!( + iface1_loc.contains("first.wit"), + "should reference first.wit, got {iface1_loc}" + ); + assert!( + iface1_loc.contains(":3:"), + "interface should be on line 3, got {iface1_loc}" + ); + + let func1_span = resolve.interfaces[iface1_id].functions["first-func"] + .span + .unwrap(); + let func1_loc = resolve.render_location(func1_span); + assert!( + func1_loc.contains("first.wit"), + "should reference first.wit, got {func1_loc}" + ); + assert!( + func1_loc.contains(":4:"), + "function should be on line 4, got {func1_loc}" + ); + + let iface2_id = resolve.packages[pkg2].interfaces["second-iface"]; + let iface2_span = resolve.interfaces[iface2_id].span.unwrap(); + let iface2_loc = resolve.render_location(iface2_span); + assert!( + iface2_loc.contains("second.wit"), + "should reference second.wit, got {iface2_loc}" + ); + assert!( + iface2_loc.contains(":3:"), + "interface should be on line 3, got {iface2_loc}" + ); + + let func2_span = resolve.interfaces[iface2_id].functions["second-func"] + .span + .unwrap(); + let func2_loc = resolve.render_location(func2_span); + assert!( + func2_loc.contains("second.wit"), + "should reference second.wit, got {func2_loc}" + ); + assert!( + func2_loc.contains(":4:"), + "function should be on line 4, got {func2_loc}" + ); + + Ok(()) + } + + #[test] + fn span_preservation_for_fields_and_cases() -> Result<()> { + use crate::TypeDefKind; + + let mut resolve = Resolve::default(); + let pkg = resolve.push_str( + "test.wit", + r#" + package foo:bar; + + interface my-iface { + record my-record { + field1: u32, + field2: string, + } + + flags my-flags { + flag1, + flag2, + } + + variant my-variant { + case1, + case2(u32), + } + + enum my-enum { + val1, + val2, + } + } + "#, + )?; + + let iface_id = resolve.packages[pkg].interfaces["my-iface"]; + + // Check record fields have spans + let record_id = resolve.interfaces[iface_id].types["my-record"]; + let TypeDefKind::Record(record) = &resolve.types[record_id].kind else { + panic!("expected record"); + }; + assert!(record.fields[0].span.is_some(), "field1 should have span"); + assert!(record.fields[1].span.is_some(), "field2 should have span"); + + // Check flags have spans + let flags_id = resolve.interfaces[iface_id].types["my-flags"]; + let TypeDefKind::Flags(flags) = &resolve.types[flags_id].kind else { + panic!("expected flags"); + }; + assert!(flags.flags[0].span.is_some(), "flag1 should have span"); + assert!(flags.flags[1].span.is_some(), "flag2 should have span"); + + // Check variant cases have spans + let variant_id = resolve.interfaces[iface_id].types["my-variant"]; + let TypeDefKind::Variant(variant) = &resolve.types[variant_id].kind else { + panic!("expected variant"); + }; + assert!(variant.cases[0].span.is_some(), "case1 should have span"); + assert!(variant.cases[1].span.is_some(), "case2 should have span"); + + // Check enum cases have spans + let enum_id = resolve.interfaces[iface_id].types["my-enum"]; + let TypeDefKind::Enum(e) = &resolve.types[enum_id].kind else { + panic!("expected enum"); + }; + assert!(e.cases[0].span.is_some(), "val1 should have span"); + assert!(e.cases[1].span.is_some(), "val2 should have span"); + + Ok(()) + } + + #[test] + fn span_preservation_for_fields_through_merge() -> Result<()> { + use crate::TypeDefKind; + + let mut resolve1 = Resolve::default(); + resolve1.push_str( + "test1.wit", + r#" + package foo:bar; + + interface iface1 { + record rec1 { + f1: u32, + } + } + "#, + )?; + + let mut resolve2 = Resolve::default(); + let pkg2 = resolve2.push_str( + "test2.wit", + r#" + package foo:baz; + + interface iface2 { + record rec2 { + f2: string, + } + + variant var2 { + c2, + } + } + "#, + )?; + + let iface2_old_id = resolve2.packages[pkg2].interfaces["iface2"]; + let rec2_old_id = resolve2.interfaces[iface2_old_id].types["rec2"]; + let var2_old_id = resolve2.interfaces[iface2_old_id].types["var2"]; + + let remap = resolve1.merge(resolve2)?; + + let rec2_id = remap.types[rec2_old_id.index()].unwrap(); + let TypeDefKind::Record(record) = &resolve1.types[rec2_id].kind else { + panic!("expected record"); + }; + assert!( + record.fields[0].span.is_some(), + "field should have span after merge" + ); + + let var2_id = remap.types[var2_old_id.index()].unwrap(); + let TypeDefKind::Variant(variant) = &resolve1.types[var2_id].kind else { + panic!("expected variant"); + }; + assert!( + variant.cases[0].span.is_some(), + "case should have span after merge" + ); + + Ok(()) + } } diff --git a/crates/wit-parser/src/sizealign.rs b/crates/wit-parser/src/sizealign.rs index 0c6acd83bb..a1d50e77a3 100644 --- a/crates/wit-parser/src/sizealign.rs +++ b/crates/wit-parser/src/sizealign.rs @@ -564,6 +564,7 @@ mod test { owner: crate::TypeOwner::None, docs: Default::default(), stability: Default::default(), + span: None, }); assert_eq!(elem.size, ArchitectureSize::new(usize::MAX, 0)); assert_eq!( @@ -584,6 +585,7 @@ mod test { owner: crate::TypeOwner::None, docs: Default::default(), stability: Default::default(), + span: None, }); obj.fill(&resolve); let my_result = crate::Result_ { @@ -596,6 +598,7 @@ mod test { owner: crate::TypeOwner::None, docs: Default::default(), stability: Default::default(), + span: None, }); assert_eq!(elem.size, ArchitectureSize::new(8, 2)); assert_eq!(elem.align, Alignment::Pointer); @@ -609,11 +612,13 @@ mod test { name: String::new(), ty: Type::String, docs: Default::default(), + span: None, }, crate::Field { name: String::new(), ty: Type::U64, docs: Default::default(), + span: None, }, ], }; @@ -623,6 +628,7 @@ mod test { owner: crate::TypeOwner::None, docs: Default::default(), stability: Default::default(), + span: None, }); assert_eq!(elem.size, ArchitectureSize::new(8, 2)); assert_eq!(elem.align, Alignment::Bytes(NonZeroUsize::new(8).unwrap()));