diff --git a/crates/stef-lsp/src/handlers/hover.rs b/crates/stef-lsp/src/handlers/hover.rs index 4d522d1..2195fc4 100644 --- a/crates/stef-lsp/src/handlers/hover.rs +++ b/crates/stef-lsp/src/handlers/hover.rs @@ -1,11 +1,11 @@ -use std::{fmt::Write, ops::Range}; +use std::{borrow::Cow, fmt::Write, ops::Range}; use anyhow::{Context, Result}; use line_index::{LineIndex, TextSize, WideLineCol}; use lsp_types::{Position, Range as LspRange}; use stef_parser::{ - Comment, Const, Definition, Enum, Fields, Module, NamedField, Schema, Span, Spanned, Struct, - TypeAlias, Variant, + Comment, Const, DataType, Definition, Enum, Fields, Module, NamedField, Schema, Span, Spanned, + Struct, Type, TypeAlias, Variant, }; pub fn visit_schema( @@ -115,8 +115,18 @@ fn visit_fields(item: &Fields<'_>, position: usize) -> Option<(String, Span)> { } fn visit_named_field(item: &NamedField<'_>, position: usize) -> Option<(String, Span)> { - (Range::from(item.name.span()).contains(&position)) - .then(|| (fold_comment(&item.comment), item.name.span())) + (Range::from(item.name.span()).contains(&position)).then(|| { + let mut text = fold_comment(&item.comment); + + let _ = write!(&mut text, "### Wire size\n\n"); + if let Some(size) = wire_size(&item.ty.value) { + size.print(&mut text, 0); + } else { + let _ = write!(&mut text, "_unknown_"); + } + + (text, item.name.span()) + }) } fn visit_alias(item: &TypeAlias<'_>, position: usize) -> Option<(String, Span)> { @@ -163,6 +173,178 @@ fn next_field_id(fields: &Fields<'_>) -> Option { } } +struct WireSize { + label: Cow<'static, str>, + min: usize, + max: Option, + inner: Vec<(Cow<'static, str>, Option)>, +} + +impl WireSize { + fn fixed(label: impl Into>, size: usize) -> Self { + Self { + label: label.into(), + min: size, + max: Some(size), + inner: Vec::new(), + } + } + + fn range(label: impl Into>, min: usize, max: usize) -> Self { + Self { + label: label.into(), + min, + max: Some(max), + inner: Vec::new(), + } + } + + fn min(label: impl Into>, min: usize) -> Self { + Self { + label: label.into(), + min, + max: None, + inner: Vec::new(), + } + } + + fn print(&self, buf: &mut String, indent: usize) { + let _ = write!(buf, "**{}** ", self.label); + + let _ = match self.max { + Some(max) if self.min == max => write!(buf, "`{max}`"), + Some(max) => write!(buf, "`{}..{max}`", self.min), + None => write!(buf, "`{}..`", self.min), + }; + + for (label, size) in &self.inner { + let _ = write!(buf, "\n{:indent$}- {label}: ", "", indent = indent + 2); + if let Some(size) = size { + size.print(buf, indent + 2); + } else { + let _ = write!(buf, "_unknown_"); + } + } + } +} + +fn wire_size(ty: &DataType<'_>) -> Option { + Some(match ty { + DataType::Bool => WireSize::fixed("bool", 1), + DataType::U8 => WireSize::fixed("u8", 1), + DataType::I8 => WireSize::fixed("i8", 1), + DataType::U16 => WireSize::range("u16", 1, 3), + DataType::I16 => WireSize::range("i16", 1, 3), + DataType::U32 => WireSize::range("u32", 1, 5), + DataType::I32 => WireSize::range("i32", 1, 5), + DataType::U64 => WireSize::range("u64", 1, 10), + DataType::I64 => WireSize::range("i64", 1, 10), + DataType::U128 => WireSize::range("u128", 1, 19), + DataType::I128 => WireSize::range("i128", 1, 19), + DataType::F32 => WireSize::fixed("f32", 4), + DataType::F64 => WireSize::fixed("f64", 8), + DataType::String => WireSize::min("string", 1), + DataType::StringRef => WireSize::min("&string", 1), + DataType::Bytes => WireSize::min("bytes", 1), + DataType::BytesRef => WireSize::min("&bytes", 1), + DataType::Vec(ty) => WireSize { + label: "vec".into(), + min: 1, + max: None, + inner: vec![ + ("length".into(), wire_size(&DataType::U64)), + ("element".into(), wire_size(&ty.value)), + ], + }, + DataType::HashMap(kv) => WireSize { + label: "hash_map".into(), + min: 1, + max: None, + inner: vec![ + ("length".into(), wire_size(&DataType::U64)), + ("key".into(), wire_size(&kv.0.value)), + ("value".into(), wire_size(&kv.1.value)), + ], + }, + DataType::HashSet(ty) => WireSize { + label: "hash_set".into(), + min: 1, + max: None, + inner: vec![ + ("length".into(), wire_size(&DataType::U64)), + ("element".into(), wire_size(&ty.value)), + ], + }, + DataType::Option(ty) => { + let inner = wire_size(&ty.value); + WireSize { + label: "option".into(), + min: 0, + max: inner.as_ref().and_then(|size| size.max).map(|max| 1 + max), + inner: vec![("value".into(), inner)], + } + } + DataType::NonZero(ty) => { + let inner = wire_size(&ty.value); + WireSize { + label: "non_zero".into(), + min: 0, + max: inner.as_ref().and_then(|size| size.max).map(|max| 1 + max), + inner: vec![("value".into(), inner)], + } + } + DataType::BoxString => WireSize::min("box", 1), + DataType::BoxBytes => WireSize::min("box", 1), + DataType::Array(ty, size) => wire_size_array(ty, *size), + DataType::Tuple(types) => wire_size_tuple(types), + DataType::External(_) => return None, + }) +} + +fn wire_size_array(ty: &Type<'_>, size: u32) -> WireSize { + let length = varint_size(size); + let inner = wire_size(&ty.value); + + WireSize { + label: "array".into(), + min: length + inner.as_ref().map_or(0, |size| size.min) * size as usize, + max: inner + .as_ref() + .and_then(|size| size.max) + .map(|max| length + max * size as usize), + inner: vec![ + ("length".into(), Some(WireSize::fixed("u64", length))), + ("element".into(), inner), + ], + } +} + +fn wire_size_tuple(types: &[Type<'_>]) -> WireSize { + let inner = types + .iter() + .enumerate() + .map(|(i, ty)| (i.to_string().into(), wire_size(&ty.value))) + .collect::>(); + + WireSize { + label: "tuple".into(), + min: inner + .iter() + .filter_map(|(_, size)| size.as_ref()) + .map(|size| size.min) + .sum(), + max: inner + .iter() + .map(|(_, size)| size.as_ref().and_then(|s| s.max)) + .sum(), + inner, + } +} + +fn varint_size(value: u32) -> usize { + ((std::mem::size_of::() * 8 - value.leading_zeros() as usize + 6) / 7).max(1) +} + #[allow(clippy::cast_possible_truncation)] fn get_range(index: &LineIndex, span: Span) -> Result { let range = Range::from(span);