From 2c7d8b3071cac47771b1d2d515752aa33dcf60b7 Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Sat, 16 Dec 2023 17:31:12 +0900 Subject: [PATCH] feat(lsp): calculate the wire size of fields on hover The min and max required byte size on the wire (when encoded) can be calculated for all types except generics and external types. This will now be shown on hover allow users to guess the size of a struct or enum variant easier. --- crates/stef-lsp/src/handlers/hover.rs | 192 +++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 5 deletions(-) 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);