diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 34b5a52a..653f99ff 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -21,6 +21,8 @@ use crate::{ ty::{NetKind, NetType}, typedef::TypedefId, }, + symbol::DefLoc, + type_infer::{BuiltinTy, Ty}, }; pub struct HirFormatter<'a> { @@ -77,6 +79,120 @@ impl HirDisplay for Arc { } } +impl HirDisplay for Ty { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + match self { + Ty::Unknown => f.write_str("unknown"), + Ty::Error => f.write_str("error"), + Ty::Void => f.write_str("void"), + Ty::Builtin(BuiltinTy::Data { id, container }) => { + InContainer::new(*container, DataTy::Builtin(*id)).hir_fmt(f) + } + Ty::Struct(struct_ref) => { + InContainer::new(struct_ref.cont_id, DataTy::Struct(*struct_ref)).hir_fmt(f) + } + Ty::Enum(def) => hir_fmt_def_backed_type(f, "enum", *def), + Ty::Union(def) => hir_fmt_def_backed_type(f, "union", *def), + Ty::Queue { elem, size } => { + elem.hir_fmt(f)?; + f.write_str(" [$")?; + if let (Some(size), Some(container)) = (size, ty_expr_container(f.db, elem)) { + f.write_str(":")?; + InContainer::new(container, *size).hir_fmt(f)?; + } + f.write_str("]") + } + Ty::Assoc { key, elem } => { + elem.hir_fmt(f)?; + f.write_str(" [")?; + key.hir_fmt(f)?; + f.write_str("]") + } + Ty::Dynamic(elem) => { + elem.hir_fmt(f)?; + f.write_str(" []") + } + Ty::Event => f.write_str("event"), + Ty::Chandle => f.write_str("chandle"), + Ty::ClassHandle { def, .. } => hir_fmt_def_backed_type(f, "class", *def), + Ty::VirtualInterface { def, modport } => { + f.write_str("virtual interface")?; + if let Some(name) = def.name(f.db) { + f.write_str(" ")?; + f.write_str(name.as_str())?; + } + if let Some(modport) = modport.and_then(|def| def.name(f.db)) { + f.write_str(".")?; + f.write_str(modport.as_str())?; + } + Ok(()) + } + Ty::Alias { typedef, target } => { + let container = typedef.cont_id.to_container(f.db); + if let Some(name) = &container.get(typedef.value).name { + f.write_str(name) + } else { + target.hir_fmt(f) + } + } + Ty::Module(module_id) => { + let module = f.db.module(*module_id); + if let Some(name) = &module.name { + f.write_str(name) + } else { + f.write_str("module") + } + } + Ty::GenerateBlock(generate_block_id) => { + let block = f.db.generate_block(*generate_block_id); + if let Some(name) = &block.name { + f.write_str(name) + } else { + f.write_str("generate block") + } + } + Ty::Block(block_id) => { + let block = f.db.block(*block_id); + if let Some(name) = &block.name { f.write_str(name) } else { f.write_str("block") } + } + } + } +} + +fn hir_fmt_def_backed_type( + f: &mut HirFormatter<'_>, + keyword: &str, + def: crate::symbol::DefId, +) -> Result<(), HirDisplayError> { + f.write_str(keyword)?; + if let DefLoc::Typedef(typedef) = def.loc(f.db) { + let container = typedef.cont_id.to_container(f.db); + if let Some(name) = &container.get(typedef.value).name { + f.write_str(" ")?; + f.write_str(name)?; + } + } + Ok(()) +} + +fn ty_expr_container(db: &dyn crate::db::HirDb, ty: &Ty) -> Option { + match ty { + Ty::Builtin(BuiltinTy::Data { container, .. }) => Some(*container), + Ty::Struct(struct_ref) => Some(struct_ref.cont_id), + Ty::Alias { typedef, .. } => Some(typedef.cont_id), + Ty::Enum(def) | Ty::Union(def) => match def.loc(db) { + DefLoc::Decl(decl) => Some(decl.cont_id), + DefLoc::Typedef(typedef) => Some(typedef.cont_id), + DefLoc::SubroutinePort(port) => Some(port.subroutine.into()), + _ => None, + }, + Ty::Queue { elem, .. } | Ty::Assoc { elem, .. } | Ty::Dynamic(elem) => { + ty_expr_container(db, elem) + } + _ => None, + } +} + impl HirDisplay for PortDirection { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match self { @@ -161,12 +277,15 @@ impl HirDisplay for InContainer { Real::RealTime => f.write_str("realtime"), }, BuiltinDataTy::String => f.write_str("string"), + BuiltinDataTy::Event => f.write_str("event"), + BuiltinDataTy::Chandle => f.write_str("chandle"), BuiltinDataTy::Void => f.write_str("void"), }, DataTy::Named(named) => match named { NamedDataTy::Ident(expr_id) => self.with_value(expr_id).hir_fmt(f), NamedDataTy::Field(expr_id) => self.with_value(expr_id).hir_fmt(f), }, + DataTy::Enum => f.write_str("enum"), DataTy::Struct(struct_ref) => { let cont = struct_ref.cont_id.to_container(f.db); let def = cont.get(struct_ref.value); @@ -511,6 +630,15 @@ impl HirDisplay for InContainer { self.with_value(end).hir_fmt(f)?; } Dimension::Size(idx) => self.with_value(idx).hir_fmt(f)?, + Dimension::Queue(size) => { + f.write_str("$")?; + if let Some(size) = size { + f.write_str(":")?; + self.with_value(size).hir_fmt(f)?; + } + } + Dimension::Assoc(key) => self.with_value(key).hir_fmt(f)?, + Dimension::Dynamic => {} } f.write_char(']') } diff --git a/crates/hir/src/hir_def/expr/data_ty.rs b/crates/hir/src/hir_def/expr/data_ty.rs index 9d420b67..9afec48f 100644 --- a/crates/hir/src/hir_def/expr/data_ty.rs +++ b/crates/hir/src/hir_def/expr/data_ty.rs @@ -1,18 +1,30 @@ use itertools::Either; use smallvec::SmallVec; use syntax::{ - SyntaxToken, TokenKind, + SyntaxNode, SyntaxToken, TokenKind, ast::{self, AstNode}, }; -use super::{ExprId, LowerExprCtx, Selector}; -use crate::{container::InContainer, hir_def::aggregate::StructId}; +use super::{Expr, ExprId, LowerExprCtx, Selector}; +use crate::{ + container::InContainer, + hir_def::{aggregate::StructId, lower_ident}, +}; +// slang exposes enum types directly as `DataType::EnumType`, while struct and +// union types share `DataType::StructUnionType` and are lowered by the owning +// declaration/typedef container into `aggregate::StructDef` with a +// `StructKind`. Unpacked dimensions carry SV array shape: `[]` is dynamic, +// `[$]`/`[$:N]` is a queue, and `[string]`/other builtin key types are +// associative. Plain `[expr]` stays a fixed-size unpacked dimension; +// typedef-key and wildcard associative arrays need scope-aware key lowering and +// are left for a later construct PR. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DataTy { Builtin(BuiltinDataTyId), Named(NamedDataTy), Struct(InContainer), + Enum, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -24,6 +36,8 @@ pub enum BuiltinDataTy { Vector { kind: VecKind, signing: bool, dimensions: SmallVec<[Option; 2]> }, Real(Real), String, + Event, + Chandle, Void, } @@ -66,6 +80,9 @@ pub enum Real { pub enum Dimension { Range(ExprId, ExprId), Size(ExprId), + Queue(Option), + Assoc(ExprId), + Dynamic, } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] @@ -77,19 +94,15 @@ pub enum NamedDataTy { impl LowerExprCtx<'_> { pub(crate) fn lower_data_ty(&mut self, ty: ast::DataType) -> DataTy { use ast::DataType::*; - let ty = match ty { - KeywordType(ty) => Either::Left(self.lower_keyword_ty(ty)), - NamedType(named_type) => Either::Right(self.lower_named_ty(named_type)), - IntegerType(ty) => Either::Left(self.lower_integer_type(ty)), - ImplicitType(ty) => Either::Left(self.lower_implicit_type(ty)), - EnumType(enum_ty) => Either::Right(self.lower_enum_type(enum_ty)), + match ty { + KeywordType(ty) => DataTy::Builtin(self.db.intern_ty(self.lower_keyword_ty(ty))), + NamedType(named_type) => DataTy::Named(self.lower_named_ty(named_type)), + IntegerType(ty) => DataTy::Builtin(self.db.intern_ty(self.lower_integer_type(ty))), + ImplicitType(ty) => DataTy::Builtin(self.db.intern_ty(self.lower_implicit_type(ty))), + EnumType(enum_ty) => self.lower_enum_type(enum_ty), StructUnionType(_) | TypeReference(_) | VirtualInterfaceType(_) => { - return self.default_data_ty(); + self.default_data_ty() } - }; - match ty { - Either::Left(ty) => DataTy::Builtin(self.db.intern_ty(ty)), - Either::Right(ty) => DataTy::Named(ty), } } @@ -101,6 +114,8 @@ impl LowerExprCtx<'_> { ShortRealType(_) => BuiltinDataTy::Real(Real::ShortReal), RealTimeType(_) => BuiltinDataTy::Real(Real::RealTime), VoidType(_) => BuiltinDataTy::Void, + EventType(_) => BuiltinDataTy::Event, + CHandleType(_) => BuiltinDataTy::Chandle, _ => BuiltinDataTy::default(), } } @@ -118,12 +133,8 @@ impl LowerExprCtx<'_> { } } - fn lower_enum_type(&mut self, _enum_ty: ast::EnumType) -> NamedDataTy { - // For now, treat enum types as implicit types - // TODO: properly handle enum member completion - // We return a missing expression since enum types are anonymous - let expr_id = self.alloc_missing(); - NamedDataTy::Ident(expr_id) + fn lower_enum_type(&mut self, _enum_ty: ast::EnumType) -> DataTy { + DataTy::Enum } fn lower_integer_type(&mut self, ty: ast::IntegerType) -> BuiltinDataTy { @@ -171,21 +182,74 @@ impl LowerExprCtx<'_> { pub(crate) fn lower_dimension(&mut self, dim: ast::VariableDimension) -> Option { use ast::DimensionSpecifier::*; - match dim.specifier()? { - RangeDimensionSpecifier(spec) => match self.lower_selector(spec.selector()) { - Selector::Bit(idx) => Some(Dimension::Size(idx)), + match dim.specifier() { + None => Some(Dimension::Dynamic), + Some(RangeDimensionSpecifier(spec)) => self.lower_range_dimension(spec), + Some(QueueDimensionSpecifier(spec)) => Some(Dimension::Queue( + spec.max_size_clause().map(|clause| self.lower_expr(clause.expr())), + )), + Some(WildcardDimensionSpecifier(_)) => None, + } + } + + fn lower_range_dimension(&mut self, spec: ast::RangeDimensionSpecifier) -> Option { + let selector = spec.selector(); + if let ast::Selector::BitSelect(bit_select) = selector { + let expr = bit_select.expr(); + if let Some(key) = Self::associative_dimension_key_token(expr) { + let expr_id = lower_ident(Some(key)) + .map(Expr::Ident) + .map(|expr| self.exprs.alloc(expr)) + .unwrap_or_else(|| self.lower_expr(expr)); + return Some(Dimension::Assoc(expr_id)); + } + Some(Dimension::Size(self.lower_expr(expr))) + } else { + match self.lower_selector(selector) { Selector::Range(left, right) => Some(Dimension::Range(left, right)), _ => None, - }, - _ => None, + } } } + fn associative_dimension_key_token(expr: ast::Expression) -> Option { + let token = first_token(expr.syntax())?; + is_builtin_dimension_key_token(token.kind()).then_some(token) + } + fn default_data_ty(&self) -> DataTy { DataTy::Builtin(self.db.intern_ty(BuiltinDataTy::default())) } } +fn first_token(node: SyntaxNode<'_>) -> Option> { + for idx in 0..node.child_count() { + if let Some(token) = node.child_token(idx) { + return Some(token); + } + if let Some(token) = node.child_node(idx).and_then(first_token) { + return Some(token); + } + } + None +} + +fn is_builtin_dimension_key_token(kind: TokenKind) -> bool { + matches!( + kind, + TokenKind::STRING_KEYWORD + | TokenKind::BYTE_KEYWORD + | TokenKind::SHORT_INT_KEYWORD + | TokenKind::INT_KEYWORD + | TokenKind::LONG_INT_KEYWORD + | TokenKind::INTEGER_KEYWORD + | TokenKind::TIME_KEYWORD + | TokenKind::BIT_KEYWORD + | TokenKind::LOGIC_KEYWORD + | TokenKind::REG_KEYWORD + ) +} + impl DataTy { pub(crate) fn is_ast_missing(ty: ast::DataType) -> bool { match ty { diff --git a/crates/hir/src/type_infer.rs b/crates/hir/src/type_infer.rs index 7efc0273..36c7c519 100644 --- a/crates/hir/src/type_infer.rs +++ b/crates/hir/src/type_infer.rs @@ -7,7 +7,7 @@ use crate::{ db::HirDb, hir_def::{ Ident, - aggregate::StructId, + aggregate::{StructId, StructKind}, declaration::Declaration, expr::{ BinaryOp, Expr, ExprId, UnaryOp, @@ -21,7 +21,7 @@ use crate::{ typedef::TypedefId, }, semantics::pathres::{PathResolution, instance_target_module_id, resolve_name}, - symbol::{DefId, DefKind, NameContext}, + symbol::{DefId, DefKind, DefLoc, NameContext}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -36,12 +36,28 @@ pub enum Ty { Void, Builtin(BuiltinTy), Struct(InContainer), + Enum(DefId), + Union(DefId), + Queue { elem: Box, size: Option }, + Assoc { key: Box, elem: Box }, + Dynamic(Box), + Event, + Chandle, + ClassHandle { def: DefId, spec_args: Vec }, + VirtualInterface { def: DefId, modport: Option }, Alias { typedef: InContainer, target: Box }, Module(ModuleId), GenerateBlock(GenerateBlockId), Block(crate::hir_def::block::BlockId), } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SpecArg { + Default, + Value(ExprId), + Type(Ty), +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct TyResult { pub ty: Ty, @@ -74,7 +90,16 @@ pub enum TyClass { } pub fn normalize_data_ty(db: &dyn HirDb, container: ScopeId, data_ty: DataTy) -> TyResult { - normalize_data_ty_inner(db, container, data_ty, &mut FxHashSet::default()) + normalize_data_ty_with_owner(db, container, data_ty, None) +} + +fn normalize_data_ty_with_owner( + db: &dyn HirDb, + container: ScopeId, + data_ty: DataTy, + owner: Option, +) -> TyResult { + normalize_data_ty_inner(db, container, data_ty, owner, &mut FxHashSet::default()) } pub(crate) fn type_of_typedef_query( @@ -111,7 +136,12 @@ fn type_of_decl_impl(db: &dyn HirDb, decl: InContainer) -> TyResult { let Some(data_ty) = data_ty_of_decl(db, decl) else { return TyResult::new(Ty::Unknown); }; - normalize_data_ty(db, decl.cont_id, data_ty) + let owner = DefId::new(db, decl); + let mut result = normalize_data_ty_with_owner(db, decl.cont_id, data_ty, Some(owner)); + if let Some(declarator) = decl_of(db, decl) { + result.ty = apply_unpacked_dimensions(db, decl.cont_id, result.ty, &declarator.dimensions); + } + result } fn type_of_path_resolution_impl(db: &dyn HirDb, res: PathResolution) -> TyResult { @@ -145,10 +175,14 @@ fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { .as_decl(db) .map(|decl| type_of_decl_impl(db, decl)) .unwrap_or_else(|| TyResult::new(Ty::Unknown)), - DefKind::Typedef | DefKind::Enum | DefKind::Struct => def_id + DefKind::Typedef | DefKind::Struct => def_id .as_typedef(db) .map(|typedef| type_of_typedef_impl(db, typedef)) .unwrap_or_else(|| TyResult::new(Ty::Unknown)), + DefKind::Enum => def_id + .as_typedef(db) + .map(|typedef| type_of_typedef_impl(db, typedef)) + .unwrap_or_else(|| TyResult::new(Ty::Enum(def_id))), DefKind::SubroutinePort => def_id .as_subroutine_port(db) .map(|port| type_of_subroutine_port_impl(db, port)) @@ -217,10 +251,22 @@ pub fn members_of_ty(db: &dyn HirDb, ty: &Ty) -> Vec { match ty { Ty::Alias { target, .. } => members_of_ty(db, target), Ty::Struct(struct_id) => struct_members(db, *struct_id), + Ty::Union(def_id) => union_members(db, *def_id), Ty::Module(module_id) => module_members(db, *module_id), Ty::GenerateBlock(generate_block_id) => generate_block_members(db, *generate_block_id), Ty::Block(block_id) => block_members(db, *block_id), - Ty::Unknown | Ty::Error | Ty::Void | Ty::Builtin(_) => Vec::new(), + Ty::Unknown + | Ty::Error + | Ty::Void + | Ty::Builtin(_) + | Ty::Enum(_) + | Ty::Queue { .. } + | Ty::Assoc { .. } + | Ty::Dynamic(_) + | Ty::Event + | Ty::Chandle + | Ty::ClassHandle { .. } + | Ty::VirtualInterface { .. } => Vec::new(), } } @@ -239,12 +285,21 @@ pub fn type_class(db: &dyn HirDb, ty: &Ty) -> Option { BuiltinDataTy::Int { .. } | BuiltinDataTy::Vector { .. } => Some(TyClass::Integral), BuiltinDataTy::Real(_) => Some(TyClass::Real), BuiltinDataTy::String => Some(TyClass::String), - BuiltinDataTy::Void => None, + BuiltinDataTy::Event | BuiltinDataTy::Chandle | BuiltinDataTy::Void => None, }, + Ty::Enum(_) => Some(TyClass::Integral), Ty::Unknown | Ty::Error | Ty::Void | Ty::Struct(_) + | Ty::Union(_) + | Ty::Queue { .. } + | Ty::Assoc { .. } + | Ty::Dynamic(_) + | Ty::Event + | Ty::Chandle + | Ty::ClassHandle { .. } + | Ty::VirtualInterface { .. } | Ty::Module(_) | Ty::GenerateBlock(_) | Ty::Block(_) => None, @@ -275,7 +330,11 @@ pub fn packed_bit_width(db: &dyn HirDb, ty: &Ty) -> Option { match ty { Ty::Alias { target, .. } => packed_bit_width(db, target), Ty::Builtin(BuiltinTy::Data { id, container }) => match db.lookup_intern_ty(*id) { - BuiltinDataTy::String | BuiltinDataTy::Real(_) | BuiltinDataTy::Void => None, + BuiltinDataTy::String + | BuiltinDataTy::Real(_) + | BuiltinDataTy::Event + | BuiltinDataTy::Chandle + | BuiltinDataTy::Void => None, BuiltinDataTy::Int { kind, .. } => Some(int_kind_width(kind) as u64), BuiltinDataTy::Vector { dimensions, .. } => { if dimensions.is_empty() { @@ -292,6 +351,9 @@ pub fn packed_bit_width(db: &dyn HirDb, ty: &Ty) -> Option { i128::abs(left - right).checked_add(1)? } Dimension::Size(size) => eval_const_i128(db, *container, size)?, + Dimension::Queue(_) | Dimension::Assoc(_) | Dimension::Dynamic => { + return None; + } }; let width: u64 = width.try_into().ok()?; product = product.checked_mul(width)?; @@ -303,6 +365,15 @@ pub fn packed_bit_width(db: &dyn HirDb, ty: &Ty) -> Option { | Ty::Error | Ty::Void | Ty::Struct(_) + | Ty::Enum(_) + | Ty::Union(_) + | Ty::Queue { .. } + | Ty::Assoc { .. } + | Ty::Dynamic(_) + | Ty::Event + | Ty::Chandle + | Ty::ClassHandle { .. } + | Ty::VirtualInterface { .. } | Ty::Module(_) | Ty::GenerateBlock(_) | Ty::Block(_) => None, @@ -313,20 +384,26 @@ fn normalize_data_ty_inner( db: &dyn HirDb, container: ScopeId, data_ty: DataTy, + owner: Option, seen: &mut FxHashSet>, ) -> TyResult { match data_ty { - DataTy::Builtin(builtin) => { - if matches!( - db.lookup_intern_ty(builtin), - crate::hir_def::expr::data_ty::BuiltinDataTy::Void - ) { - TyResult::new(Ty::Void) - } else { - TyResult::new(Ty::Builtin(BuiltinTy::Data { id: builtin, container })) - } + DataTy::Builtin(builtin) => match db.lookup_intern_ty(builtin) { + BuiltinDataTy::Void => TyResult::new(Ty::Void), + BuiltinDataTy::Event => TyResult::new(Ty::Event), + BuiltinDataTy::Chandle => TyResult::new(Ty::Chandle), + _ => TyResult::new(Ty::Builtin(BuiltinTy::Data { id: builtin, container })), + }, + DataTy::Struct(struct_id) => match struct_kind(db, struct_id) { + Some(StructKind::Union) => owner + .map(Ty::Union) + .map(TyResult::new) + .unwrap_or_else(|| TyResult::new(Ty::Unknown)), + Some(StructKind::Struct) | None => TyResult::new(Ty::Struct(struct_id)), + }, + DataTy::Enum => { + owner.map(Ty::Enum).map(TyResult::new).unwrap_or_else(|| TyResult::new(Ty::Unknown)) } - DataTy::Struct(struct_id) => TyResult::new(Ty::Struct(struct_id)), DataTy::Named(named) => type_of_named_data_ty(db, container, named, seen), } } @@ -378,7 +455,8 @@ fn type_of_typedef_inner( return TyResult::new(Ty::Unknown); }; - let mut target = normalize_data_ty_inner(db, typedef.cont_id, data_ty, seen); + let owner = DefId::new(db, typedef); + let mut target = normalize_data_ty_inner(db, typedef.cont_id, data_ty, Some(owner), seen); seen.remove(&typedef); let ty = if matches!(target.ty, Ty::Error) { Ty::Error @@ -406,6 +484,96 @@ fn struct_members(db: &dyn HirDb, struct_id: InContainer) -> Vec Vec { + aggregate_struct_id_from_def(db, def_id) + .filter(|struct_id| struct_kind(db, *struct_id) == Some(StructKind::Union)) + .map(|struct_id| struct_members(db, struct_id)) + .unwrap_or_default() +} + +fn aggregate_struct_id_from_def(db: &dyn HirDb, def_id: DefId) -> Option> { + match def_id.loc(db) { + DefLoc::Typedef(typedef) => match typedef_of(db, typedef)?.ty? { + DataTy::Struct(struct_id) => Some(struct_id), + _ => None, + }, + DefLoc::Decl(decl) => match data_ty_of_decl(db, decl)? { + DataTy::Struct(struct_id) => Some(struct_id), + _ => None, + }, + _ => None, + } +} + +fn struct_kind(db: &dyn HirDb, struct_id: InContainer) -> Option { + struct_of(db, struct_id).map(|def| def.kind) +} + +fn apply_unpacked_dimensions( + db: &dyn HirDb, + container: ScopeId, + mut ty: Ty, + dimensions: &[Option], +) -> Ty { + for dim in dimensions.iter().flatten() { + ty = match dim { + Dimension::Queue(size) => Ty::Queue { elem: Box::new(ty), size: *size }, + Dimension::Assoc(key) => Ty::Assoc { + key: Box::new(type_of_dimension_key(db, container, *key)), + elem: Box::new(ty), + }, + Dimension::Dynamic => Ty::Dynamic(Box::new(ty)), + Dimension::Size(key) if builtin_dimension_key_ty(db, container, *key).is_some() => { + Ty::Assoc { + key: Box::new(type_of_dimension_key(db, container, *key)), + elem: Box::new(ty), + } + } + Dimension::Range(_, _) | Dimension::Size(_) => ty, + }; + } + ty +} + +fn type_of_dimension_key(db: &dyn HirDb, container: ScopeId, expr_id: ExprId) -> Ty { + if let Some(ty) = builtin_dimension_key_ty(db, container, expr_id) { + return ty; + } + type_of_expr_impl(db, InContainer::new(container, expr_id)).ty +} + +fn builtin_dimension_key_ty(db: &dyn HirDb, container: ScopeId, expr_id: ExprId) -> Option { + if let Some(Expr::Ident(ident)) = expr_of(db, InContainer::new(container, expr_id)) { + return builtin_type_name_ty(db, container, &ident); + } + None +} + +fn builtin_type_name_ty(db: &dyn HirDb, container: ScopeId, ident: &Ident) -> Option { + let ty = match ident.as_str() { + "string" => BuiltinDataTy::String, + "byte" => BuiltinDataTy::Int { kind: IntKind::Byte, signing: true }, + "shortint" => BuiltinDataTy::Int { kind: IntKind::ShortInt, signing: true }, + "int" => BuiltinDataTy::Int { kind: IntKind::Int, signing: true }, + "longint" => BuiltinDataTy::Int { kind: IntKind::LongInt, signing: true }, + "integer" => BuiltinDataTy::Int { kind: IntKind::Integer, signing: true }, + "time" => BuiltinDataTy::Int { kind: IntKind::Time, signing: false }, + "bit" => BuiltinDataTy::Vector { + kind: crate::hir_def::expr::data_ty::VecKind::Bit, + signing: false, + dimensions: Default::default(), + }, + "logic" => BuiltinDataTy::default(), + "reg" => BuiltinDataTy::Vector { + kind: crate::hir_def::expr::data_ty::VecKind::Reg, + signing: false, + dimensions: Default::default(), + }, + _ => return None, + }; + Some(Ty::Builtin(BuiltinTy::Data { id: db.intern_ty(ty), container })) +} + fn module_members(db: &dyn HirDb, module_id: ModuleId) -> Vec { let file = db.hir_file(crate::file::HirFileId::File(module_id.file_id())); let scope = if file.get(module_id.value).kind == ModuleKind::Package { @@ -500,7 +668,14 @@ fn type_of_subroutine_port_impl(db: &dyn HirDb, port: InSubroutine, + } + + impl salsa::Database for TestDb {} + + impl fmt::Debug for TestDb { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TestDb").finish() + } + } + + impl FileLoader for TestDb { + fn resolve_path(&self, path: AnchoredPath<'_>) -> Option { + let source_root_id = SourceRootDb::source_root_id(self, path.anchor_id); + SourceRootDb::source_root(self, source_root_id).resolve_path(path) + } + } + + fn db_with_root_text(root_text: &str) -> TestDb { + let top_path = abs_path("rtl/top.sv"); + let mut file_set = FileSet::default(); + file_set.insert(TOP, VfsPath::from(top_path.clone())); + let root = SourceRoot::new_local_with_source_files(file_set, vec![TOP]); + let mut files = FxHashSet::default(); + files.insert(TOP); + + let preprocess = PreprocessConfig::default(); + let project_config = ProjectConfig::new( + vec![Some(PROFILE)], + vec![CompilationProfile { + source_roots: vec![ROOT], + top_modules: Vec::new(), + preprocess: preprocess.clone(), + }], + ); + + let mut db = TestDb::default(); + db.set_files_with_durability(Box::new(files), Durability::HIGH); + db.set_project_config_with_durability(Arc::new(project_config), Durability::HIGH); + db.set_diagnostics_config_with_durability( + Arc::new(DiagnosticsConfig::default()), + Durability::HIGH, + ); + db.set_source_root_with_durability(ROOT, Arc::new(root), Durability::LOW); + db.set_source_root_id_with_durability(TOP, ROOT, Durability::LOW); + db.set_file_path_with_durability(TOP, Some(top_path), Durability::LOW); + db.set_file_kind_with_durability(TOP, SourceFileKind::SystemVerilog, Durability::LOW); + db.set_file_text_with_durability(TOP, Arc::from(root_text), Durability::LOW); + db.set_file_preprocess_config_with_durability(TOP, Arc::new(preprocess), Durability::LOW); + db + } + + fn abs_path(path: &str) -> AbsPathBuf { + let prefix = if cfg!(windows) { "C:/repo" } else { "/repo" }; + AbsPathBuf::assert(Utf8PathBuf::from(format!("{prefix}/{path}"))) + } + + fn ident(name: &str) -> Ident { + SmolStr::new(name) + } + + fn module_id(db: &TestDb, name: &str) -> ModuleId { + db.unit_scope() + .module_ids(db, &ident(name)) + .unique() + .expect("module should resolve uniquely") + } + + fn decl_ty(db: &TestDb, module_id: ModuleId, name: &str) -> Ty { + let res = resolve_name(db, module_id.into(), &ident(name), NameContext::Value) + .expect("declaration should resolve"); + let decl = res + .def_ids() + .iter() + .find_map(|def_id| def_id.as_decl(db)) + .expect("resolved value should include a declaration"); + db.type_of_decl(decl).ty.clone() + } + + fn typedef_ty(db: &TestDb, module_id: ModuleId, name: &str) -> Ty { + let res = resolve_name(db, module_id.into(), &ident(name), NameContext::Type) + .expect("typedef should resolve"); + let typedef = res + .def_ids() + .iter() + .find_map(|def_id| def_id.as_typedef(db)) + .expect("resolved type should include a typedef"); + db.type_of_typedef(typedef).ty.clone() + } + + fn assert_owner_is_decl(db: &TestDb, def: DefId, name: &str) { + let DefLoc::Decl(decl) = def.loc(db) else { + panic!("expected {name} owner to be a declaration"); + }; + assert_eq!(decl.cont_id.to_container(db).get(decl.value).name.as_deref(), Some(name)); + } + + fn assert_owner_is_typedef(db: &TestDb, def: DefId, name: &str) { + let DefLoc::Typedef(typedef) = def.loc(db) else { + panic!("expected {name} owner to be a typedef"); + }; + assert_eq!(typedef.cont_id.to_container(db).get(typedef.value).name.as_deref(), Some(name)); + } + + #[test] + fn sv_type_adt_covers_enum_union_and_unpacked_array_kinds() { + let db = db_with_root_text( + r#" +module m; + typedef enum logic [1:0] { A, B } state_t; + enum { C, D } anon_enum; + typedef union packed { logic [7:0] byte_v; int int_v; } payload_u; + logic queue_var[$]; + logic bounded_queue[$:4]; + logic assoc_var[string]; + logic dyn_var[]; + event ev; + chandle handle; +endmodule +"#, + ); + let module_id = module_id(&db, "m"); + + let Ty::Alias { target: state_target, .. } = typedef_ty(&db, module_id, "state_t") else { + panic!("state_t should infer as an alias"); + }; + let Ty::Enum(state_def) = *state_target else { + panic!("state_t target should be an enum"); + }; + assert_owner_is_typedef(&db, state_def, "state_t"); + + let Ty::Enum(anon_enum_def) = decl_ty(&db, module_id, "anon_enum") else { + panic!("anonymous enum declaration should infer as Ty::Enum"); + }; + assert_owner_is_decl(&db, anon_enum_def, "anon_enum"); + + let Ty::Alias { target: payload_target, .. } = typedef_ty(&db, module_id, "payload_u") + else { + panic!("payload_u should infer as an alias"); + }; + let Ty::Union(payload_def) = *payload_target else { + panic!("payload_u target should be a union"); + }; + assert_owner_is_typedef(&db, payload_def, "payload_u"); + + assert!(matches!(decl_ty(&db, module_id, "queue_var"), Ty::Queue { size: None, .. })); + assert!(matches!( + decl_ty(&db, module_id, "bounded_queue"), + Ty::Queue { size: Some(_), .. } + )); + assert!(matches!(decl_ty(&db, module_id, "assoc_var"), Ty::Assoc { .. })); + assert!(matches!(decl_ty(&db, module_id, "dyn_var"), Ty::Dynamic(_))); + assert!(matches!(decl_ty(&db, module_id, "ev"), Ty::Event)); + assert!(matches!(decl_ty(&db, module_id, "handle"), Ty::Chandle)); + } + + #[test] + fn sv_type_adt_display_covers_new_variants() { + let db = db_with_root_text( + r#" +module m; + typedef enum { A, B } state_t; + typedef union packed { logic [7:0] byte_v; int int_v; } payload_u; + logic queue_var[$]; + logic bounded_queue[$:4]; + logic assoc_var[string]; + logic dyn_var[]; + event ev; + chandle handle; +endmodule +"#, + ); + let module_id = module_id(&db, "m"); + + let rendered = [ + typedef_ty(&db, module_id, "state_t").display_source(&db).unwrap(), + typedef_ty(&db, module_id, "payload_u").display_source(&db).unwrap(), + decl_ty(&db, module_id, "queue_var").display_source(&db).unwrap(), + decl_ty(&db, module_id, "bounded_queue").display_source(&db).unwrap(), + decl_ty(&db, module_id, "assoc_var").display_source(&db).unwrap(), + decl_ty(&db, module_id, "dyn_var").display_source(&db).unwrap(), + decl_ty(&db, module_id, "ev").display_source(&db).unwrap(), + decl_ty(&db, module_id, "handle").display_source(&db).unwrap(), + ]; + + assert_eq!( + rendered, + [ + "state_t", + "payload_u", + "logic [$]", + "logic [$:4]", + "logic [string]", + "logic []", + "event", + "chandle", + ] + ); + } +} diff --git a/crates/ide/src/code_action/handlers/extract_variable.rs b/crates/ide/src/code_action/handlers/extract_variable.rs index 0ac1507b..6ae43ad4 100644 --- a/crates/ide/src/code_action/handlers/extract_variable.rs +++ b/crates/ide/src/code_action/handlers/extract_variable.rs @@ -1,21 +1,12 @@ use std::ops::Range; -use hir::{ - base_db::source_db::SourceDb, - container::InContainer, - db::HirDb, - display::HirDisplay, - type_infer::{BuiltinTy, Ty}, -}; +use hir::{base_db::source_db::SourceDb, db::HirDb, display::HirDisplay, type_infer::Ty}; use syntax::{ SyntaxAncestors, SyntaxKind, TokenKind, WalkEvent, ast::{self, AstNode}, has_text_range::HasTextRange, }; -use utils::{ - get::GetRef, - text_edit::{TextRange, TextSize}, -}; +use utils::text_edit::{TextRange, TextSize}; use crate::code_action::{ CodeActionCollector, CodeActionCtx, CodeActionId, CodeActionKind, line_indent, @@ -196,25 +187,13 @@ fn expected_type_for_assignment_rhs( fn render_ty(ctx: &CodeActionCtx<'_>, ty: &Ty) -> Option { match ty { - Ty::Builtin(BuiltinTy::Data { id, container }) => { - InContainer::new(*container, hir::hir_def::expr::data_ty::DataTy::Builtin(*id)) - .display_source(ctx.sema().db) - .ok() - } - Ty::Alias { typedef, .. } => { - let container = typedef.cont_id.to_container(ctx.sema().db); - container.get(typedef.value).name.as_ref().map(ToString::to_string) - } - Ty::Struct(struct_ref) => { - let container = struct_ref.cont_id.to_container(ctx.sema().db); - container.get(struct_ref.value).name.as_ref().map(ToString::to_string) - } Ty::Unknown | Ty::Error | Ty::Void | Ty::Module(_) | Ty::GenerateBlock(_) | Ty::Block(_) => None, + _ => ty.display_source(ctx.sema().db).ok(), } } diff --git a/crates/ide/src/semantic_tokens.rs b/crates/ide/src/semantic_tokens.rs index 5e00c29e..fab554a4 100644 --- a/crates/ide/src/semantic_tokens.rs +++ b/crates/ide/src/semantic_tokens.rs @@ -634,7 +634,7 @@ fn scoped_uses_dot(scoped: ast::ScopedName<'_>) -> bool { fn named_data_ty_expr_id(ty: DataTy) -> Option { match ty { DataTy::Named(NamedDataTy::Ident(expr_id) | NamedDataTy::Field(expr_id)) => Some(expr_id), - DataTy::Builtin(_) | DataTy::Struct(_) => None, + DataTy::Builtin(_) | DataTy::Struct(_) | DataTy::Enum => None, } }