diff --git a/external-crates/move/crates/move-compiler/src/parser/syntax.rs b/external-crates/move/crates/move-compiler/src/parser/syntax.rs index e0b3104362e8f..74f3e75c80422 100644 --- a/external-crates/move/crates/move-compiler/src/parser/syntax.rs +++ b/external-crates/move/crates/move-compiler/src/parser/syntax.rs @@ -3428,6 +3428,7 @@ fn parse_enum_variant_decls( // Parse an enum variant definition: // VariantDecl = ("{" Comma "}" | "(" Comma ")") fn parse_enum_variant_decl(context: &mut Context) -> Result> { + context.tokens.match_doc_comments(); let start_loc = context.tokens.start_loc(); let name = parse_identifier(context)?; let fields = parse_enum_variant_fields(context)?; diff --git a/external-crates/move/crates/move-docgen/src/docgen.rs b/external-crates/move/crates/move-docgen/src/docgen.rs index acb803f56b248..8aec76705e526 100644 --- a/external-crates/move/crates/move-docgen/src/docgen.rs +++ b/external-crates/move/crates/move-docgen/src/docgen.rs @@ -13,8 +13,8 @@ use move_model::{ code_writer::{CodeWriter, CodeWriterLabel}, emit, emitln, model::{ - AbilitySet, FunId, FunctionEnv, GlobalEnv, Loc, ModuleEnv, ModuleId, NamedConstantEnv, - Parameter, QualifiedId, StructEnv, TypeParameter, + AbilitySet, EnumEnv, FunId, FunctionEnv, GlobalEnv, Loc, ModuleEnv, ModuleId, + NamedConstantEnv, Parameter, QualifiedId, StructEnv, TypeParameter, }, symbol::Symbol, ty::TypeDisplayContext, @@ -584,6 +584,15 @@ impl<'env> Docgen<'env> { } } + if !module_env.get_enums().count() > 0 { + for s in module_env + .get_enums() + .sorted_by(|a, b| Ord::cmp(&a.get_loc(), &b.get_loc())) + { + self.gen_enum(&s); + } + } + if module_env.get_named_constant_count() > 0 { // Introduce a Constant section self.gen_named_constants(); @@ -896,6 +905,28 @@ impl<'env> Docgen<'env> { self.decrement_section_nest(); } + /// Generates documentation for an enum. + fn gen_enum(&self, enum_env: &EnumEnv<'_>) { + let name = enum_env.get_name(); + self.section_header( + &format!("Enum `{}`", self.name_string(enum_env.get_name())), + &self.label_for_module_item(&enum_env.module_env, name), + ); + self.increment_section_nest(); + self.doc_text(enum_env.get_doc()); + self.code_block(&self.enum_header_display(enum_env)); + + if self.options.include_impl || (self.options.include_specs && self.options.specs_inlined) { + // Include field documentation if either impls or specs are present and inlined, + // because they are used by both. + self.begin_collapsed("Variants"); + self.gen_enum_variants(enum_env); + self.end_collapsed(); + } + + self.decrement_section_nest(); + } + /// Returns "Struct `N`" or "Resource `N`". fn struct_title(&self, struct_env: &StructEnv<'_>) -> String { // NOTE(mengxu): although we no longer declare structs with the `resource` keyword, it @@ -960,6 +991,59 @@ impl<'env> Docgen<'env> { self.end_definitions(); } + /// Generates code signature for an enum. + fn enum_header_display(&self, enum_env: &EnumEnv<'_>) -> String { + let name = self.name_string(enum_env.get_name()); + let type_params = self.type_parameter_list_display(&enum_env.get_named_type_parameters()); + let ability_tokens = self.ability_tokens(enum_env.get_abilities()); + if ability_tokens.is_empty() { + format!("public enum {}{}", name, type_params) + } else { + format!( + "public enum {}{} has {}", + name, + type_params, + ability_tokens.join(", ") + ) + } + } + + fn gen_enum_variants(&self, enum_env: &EnumEnv<'_>) { + let tctx = { + let type_param_names = Some( + enum_env + .get_named_type_parameters() + .iter() + .map(|TypeParameter(name, _)| *name) + .collect_vec(), + ); + TypeDisplayContext::WithEnv { + env: self.env, + type_param_names, + } + }; + self.begin_definitions(); + for variant_env in enum_env.get_variants() { + self.definition_text( + &format!("Variant `{}`", self.name_string(variant_env.get_name()),), + variant_env.get_doc(), + ); + for field in variant_env.get_fields() { + self.begin_definitions(); + self.definition_text( + &format!( + "`{}: {}`", + self.name_string(field.get_name()), + field.get_type().display(&tctx) + ), + field.get_doc(), + ); + self.end_definitions(); + } + } + self.end_definitions(); + } + /// Generates documentation for a function. fn gen_function(&self, func_env: &FunctionEnv<'_>) { let is_script = func_env.module_env.is_script_module(); diff --git a/external-crates/move/crates/move-docgen/tests/sources/enums_test.move b/external-crates/move/crates/move-docgen/tests/sources/enums_test.move new file mode 100644 index 0000000000000..93f04ca442284 --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/enums_test.move @@ -0,0 +1,18 @@ +/// This is a doc comment above an annotation. +#[allow(unused_const)] +module 0x42::m { + /// This is a doc comment above an enum + public enum Enum { + /// This is a doc comment above a variant + A, + B(), + C(u64), + /// Another doc comment + D { + /// Doc text on variant field + x: u64 + }, + E { x: u64, y: u64 }, + } +} + diff --git a/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_inline.md b/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_inline.md new file mode 100644 index 0000000000000..d9e02630a316e --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_inline.md @@ -0,0 +1,106 @@ + + + +# Module `0x42::m` + +This is a doc comment above an annotation. + + +- [Enum `Enum`](#0x42_m_Enum) + + +
+ + + + + +## Enum `Enum` + +This is a doc comment above an enum + + +
public enum Enum
+
+ + + +
+Variants + + +
+
+Variant A +
+
+ This is a doc comment above a variant +
+
+Variant B +
+
+ +
+
+Variant C +
+
+ +
+ +
+
+pos0: u64 +
+
+ +
+
+ +
+Variant D +
+
+ Another doc comment +
+ +
+
+x: u64 +
+
+ Doc text on variant field +
+
+ +
+Variant E +
+
+ +
+ +
+
+x: u64 +
+
+ +
+
+ + +
+
+y: u64 +
+
+ +
+
+ +
+ + +
diff --git a/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_inline_no_fold.md b/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_inline_no_fold.md new file mode 100644 index 0000000000000..c13b9c83b1ea0 --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_inline_no_fold.md @@ -0,0 +1,102 @@ + + + +# Module `0x42::m` + +This is a doc comment above an annotation. + + +- [Enum `Enum`](#0x42_m_Enum) + + +
+ + + + + +## Enum `Enum` + +This is a doc comment above an enum + + +
public enum Enum
+
+ + + +##### Variants + + +
+
+Variant A +
+
+ This is a doc comment above a variant +
+
+Variant B +
+
+ +
+
+Variant C +
+
+ +
+ +
+
+pos0: u64 +
+
+ +
+
+ +
+Variant D +
+
+ Another doc comment +
+ +
+
+x: u64 +
+
+ Doc text on variant field +
+
+ +
+Variant E +
+
+ +
+ +
+
+x: u64 +
+
+ +
+
+ + +
+
+y: u64 +
+
+ +
+
+ +
diff --git a/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_separate.md b/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_separate.md new file mode 100644 index 0000000000000..d9e02630a316e --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/enums_test.spec_separate.md @@ -0,0 +1,106 @@ + + + +# Module `0x42::m` + +This is a doc comment above an annotation. + + +- [Enum `Enum`](#0x42_m_Enum) + + +
+ + + + + +## Enum `Enum` + +This is a doc comment above an enum + + +
public enum Enum
+
+ + + +
+Variants + + +
+
+Variant A +
+
+ This is a doc comment above a variant +
+
+Variant B +
+
+ +
+
+Variant C +
+
+ +
+ +
+
+pos0: u64 +
+
+ +
+
+ +
+Variant D +
+
+ Another doc comment +
+ +
+
+x: u64 +
+
+ Doc text on variant field +
+
+ +
+Variant E +
+
+ +
+ +
+
+x: u64 +
+
+ +
+
+ + +
+
+y: u64 +
+
+ +
+
+ +
+ + +
diff --git a/external-crates/move/crates/move-model/src/builder/exp_translator.rs b/external-crates/move/crates/move-model/src/builder/exp_translator.rs index 96b33e6f979a1..b6538796856a3 100644 --- a/external-crates/move/crates/move-model/src/builder/exp_translator.rs +++ b/external-crates/move/crates/move-model/src/builder/exp_translator.rs @@ -132,7 +132,7 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo fn type_display_context(&self) -> TypeDisplayContext<'_> { TypeDisplayContext::WithoutEnv { symbol_pool: self.symbol_pool(), - reverse_struct_table: &self.parent.parent.reverse_struct_table, + reverse_struct_table: &self.parent.parent.reverse_datatype_table, } } diff --git a/external-crates/move/crates/move-model/src/builder/model_builder.rs b/external-crates/move/crates/move-model/src/builder/model_builder.rs index 4a823ed5a50c2..73ffd986c2f37 100644 --- a/external-crates/move/crates/move-model/src/builder/model_builder.rs +++ b/external-crates/move/crates/move-model/src/builder/model_builder.rs @@ -27,26 +27,37 @@ use crate::{ pub(crate) struct ModelBuilder<'env> { /// The global environment we are building. pub env: &'env mut GlobalEnv, - /// A symbol table for structs. - pub struct_table: BTreeMap, - /// A reverse mapping from ModuleId/StructId pairs to QualifiedSymbol. This + /// A symbol table for datatypes. + pub datatype_table: BTreeMap, + /// A reverse mapping from ModuleId/DatatypeId pairs to QualifiedSymbol. This /// is used for visualization of types in error messages. - pub reverse_struct_table: BTreeMap<(ModuleId, DatatypeId), QualifiedSymbol>, + pub reverse_datatype_table: BTreeMap<(ModuleId, DatatypeId), QualifiedSymbol>, /// A symbol table for functions. pub fun_table: BTreeMap, /// A symbol table for constants. pub const_table: BTreeMap, } -/// A declaration of a struct. +/// A declaration of a datatype. #[derive(Debug, Clone)] -pub(crate) struct StructEntry { +pub(crate) struct DatatypeEntry { pub loc: Loc, pub module_id: ModuleId, pub struct_id: DatatypeId, pub type_params: Vec<(Symbol, Type)>, - pub fields: Option>, pub attributes: Vec, + pub data: DatatypeData, +} + +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub(crate) enum DatatypeData { + Struct { + fields: Option>, + }, + Enum { + variants: BTreeMap>>, + }, } /// A declaration of a function. @@ -70,8 +81,8 @@ impl<'env> ModelBuilder<'env> { pub fn new(env: &'env mut GlobalEnv) -> Self { ModelBuilder { env, - struct_table: BTreeMap::new(), - reverse_struct_table: BTreeMap::new(), + datatype_table: BTreeMap::new(), + reverse_datatype_table: BTreeMap::new(), fun_table: BTreeMap::new(), const_table: BTreeMap::new(), } @@ -98,17 +109,41 @@ impl<'env> ModelBuilder<'env> { type_params: Vec<(Symbol, Type)>, fields: Option>, ) { - let entry = StructEntry { + let entry = DatatypeEntry { + loc, + attributes, + module_id, + struct_id, + type_params, + data: DatatypeData::Struct { fields }, + }; + // Duplicate declarations have been checked by the Move compiler. + assert!(self.datatype_table.insert(name.clone(), entry).is_none()); + self.reverse_datatype_table + .insert((module_id, struct_id), name); + } + + pub fn define_enum( + &mut self, + loc: Loc, + attributes: Vec, + name: QualifiedSymbol, + module_id: ModuleId, + struct_id: DatatypeId, + type_params: Vec<(Symbol, Type)>, + variants: BTreeMap>>, + ) { + let entry = DatatypeEntry { loc, attributes, module_id, struct_id, type_params, - fields, + data: DatatypeData::Enum { variants }, }; // Duplicate declarations have been checked by the Move compiler. - assert!(self.struct_table.insert(name.clone(), entry).is_none()); - self.reverse_struct_table + assert!(self.datatype_table.insert(name.clone(), entry).is_none()); + self.reverse_datatype_table .insert((module_id, struct_id), name); } @@ -149,7 +184,7 @@ impl<'env> ModelBuilder<'env> { /// Looks up a type (struct), reporting an error if it is not found. pub fn lookup_type(&self, loc: &Loc, name: &QualifiedSymbol) -> Type { - self.struct_table + self.datatype_table .get(name) .cloned() .map(|e| Type::Datatype(e.module_id, e.struct_id, project_2nd(&e.type_params))) diff --git a/external-crates/move/crates/move-model/src/builder/module_builder.rs b/external-crates/move/crates/move-model/src/builder/module_builder.rs index 72d7e31e94762..69c8112b85db0 100644 --- a/external-crates/move/crates/move-model/src/builder/module_builder.rs +++ b/external-crates/move/crates/move-model/src/builder/module_builder.rs @@ -22,7 +22,7 @@ use crate::{ ast::{Attribute, AttributeValue, ModuleName, QualifiedSymbol, Value}, builder::{ exp_translator::ExpTranslator, - model_builder::{ConstEntry, ModelBuilder}, + model_builder::{ConstEntry, DatatypeData, ModelBuilder}, }, model::{ DatatypeId, EnumData, FunId, FunctionData, Loc, ModuleId, NamedConstantData, @@ -257,6 +257,9 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { for (name, struct_def) in module_def.structs.key_cloned_iter() { self.decl_ana_struct(&name, struct_def); } + for (name, enum_def) in module_def.enums.key_cloned_iter() { + self.decl_ana_enum(&name, enum_def); + } for (name, fun_def) in module_def.functions.key_cloned_iter() { if fun_def.macro_.is_none() { self.decl_ana_fun(&name, fun_def); @@ -311,6 +314,24 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { ); } + fn decl_ana_enum(&mut self, name: &PA::DatatypeName, def: &EA::EnumDefinition) { + let qsym = self.qualified_by_module_from_name(&name.0); + let struct_id = DatatypeId::new(qsym.symbol); + let attrs = self.translate_attributes(&def.attributes); + let mut et = ExpTranslator::new(self); + let type_params = + et.analyze_and_add_type_params(def.type_parameters.iter().map(|param| ¶m.name)); + et.parent.parent.define_enum( + et.to_loc(&def.loc), + attrs, + qsym, + et.parent.module_id, + struct_id, + type_params, + BTreeMap::new(), // will be filled in during definition analysis + ); + } + fn decl_ana_fun(&mut self, name: &PA::FunctionName, def: &EA::Function) { let qsym = self.qualified_by_module_from_name(&name.0); let attrs = self.translate_attributes(&def.attributes); @@ -342,6 +363,11 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { self.def_ana_struct(&name, def); } + // Analyze all enums. + for (name, def) in module_def.enums.key_cloned_iter() { + self.def_ana_enum(&name, def); + } + // Analyze all functions. for (name, fun_def) in module_def.functions.key_cloned_iter() { if fun_def.macro_.is_none() { @@ -359,14 +385,14 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { } } -/// ## Struct Definition Analysis +/// ## Struct and Enum Definition Analysis impl<'env, 'translator> ModuleBuilder<'env, 'translator> { fn def_ana_struct(&mut self, name: &PA::DatatypeName, def: &EA::StructDefinition) { let qsym = self.qualified_by_module_from_name(&name.0); let type_params = self .parent - .struct_table + .datatype_table .get(&qsym) .expect("struct invalid") .type_params @@ -399,10 +425,61 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { EA::StructFields::Native(_) => None, }; self.parent - .struct_table + .datatype_table .get_mut(&qsym) .expect("struct invalid") - .fields = fields; + .data = DatatypeData::Struct { fields }; + } + + fn def_ana_enum(&mut self, name: &PA::DatatypeName, def: &EA::EnumDefinition) { + let qsym = self.qualified_by_module_from_name(&name.0); + let type_params = self + .parent + .datatype_table + .get(&qsym) + .expect("enum invalid") + .type_params + .clone(); + let mut et = ExpTranslator::new(self); + let loc = et.to_loc(&name.0.loc); + for (name, ty) in type_params { + et.define_type_param(&loc, name, ty); + } + let variants: BTreeMap<_, _> = def + .variants + .key_cloned_iter() + .map(|(key, variant)| { + let variant_name = et.symbol_pool().make(&key.0.value); + let variant_fields = match &variant.fields { + EA::VariantFields::Named(fields) => { + let mut field_map = BTreeMap::new(); + for (_name_loc, field_name_, (idx, ty)) in fields { + let field_sym = et.symbol_pool().make(field_name_); + let field_ty = et.translate_type(ty); + field_map.insert(field_sym, (*idx, field_ty)); + } + Some(field_map) + } + EA::VariantFields::Positional(tys) => { + let mut field_map = BTreeMap::new(); + for (idx, ty) in tys.iter().enumerate() { + let field_name_ = format!("{idx}"); + let field_sym = et.symbol_pool().make(&field_name_); + let field_ty = et.translate_type(ty); + field_map.insert(field_sym, (idx, field_ty)); + } + Some(field_map) + } + EA::VariantFields::Empty => None, + }; + (variant_name, variant_fields) + }) + .collect(); + self.parent + .datatype_table + .get_mut(&qsym) + .expect("enum invalid") + .data = DatatypeData::Enum { variants }; } } @@ -453,7 +530,7 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { let name = self.symbol_pool().make(module.identifier_at(handle.name).as_str()); if let Some(entry) = self .parent - .struct_table + .datatype_table .get(&self.qualified_by_module(name)) { Some(( @@ -482,7 +559,7 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { let name = self.symbol_pool().make(module.identifier_at(handle.name).as_str()); if let Some(entry) = self .parent - .struct_table + .datatype_table .get(&self.qualified_by_module(name)) { Some(( @@ -492,6 +569,7 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { def_idx, name, entry.loc.clone(), + Some(&source_map), entry.attributes.clone(), ), )) diff --git a/external-crates/move/crates/move-model/src/lib.rs b/external-crates/move/crates/move-model/src/lib.rs index b864b996d38db..a81e9e5557e42 100644 --- a/external-crates/move/crates/move-model/src/lib.rs +++ b/external-crates/move/crates/move-model/src/lib.rs @@ -18,7 +18,7 @@ use num::{BigUint, Num}; use builder::module_builder::ModuleBuilder; use move_binary_format::file_format::{ - CompiledModule, FunctionDefinitionIndex, StructDefinitionIndex, + CompiledModule, EnumDefinitionIndex, FunctionDefinitionIndex, StructDefinitionIndex, }; use move_compiler::{ self, @@ -335,6 +335,18 @@ pub fn run_bytecode_model_builder<'a>( module_data.struct_idx_to_id.insert(def_idx, struct_id); } + // add enums + for (i, def) in m.enum_defs().iter().enumerate() { + let def_idx = EnumDefinitionIndex(i as u16); + let name = m.identifier_at(m.datatype_handle_at(def.enum_handle).name); + let symbol = env.symbol_pool().make(name.as_str()); + let enum_id = DatatypeId::new(symbol); + let data = + env.create_move_enum_data(m, def_idx, symbol, Loc::default(), None, Vec::default()); + module_data.enum_data.insert(enum_id, data); + module_data.enum_idx_to_id.insert(def_idx, enum_id); + } + env.module_data.push(module_data); } Ok(env) diff --git a/external-crates/move/crates/move-model/src/model.rs b/external-crates/move/crates/move-model/src/model.rs index 006348d0cffb0..f701d4b94260f 100644 --- a/external-crates/move/crates/move-model/src/model.rs +++ b/external-crates/move/crates/move-model/src/model.rs @@ -1027,16 +1027,11 @@ impl GlobalEnv { def_idx: EnumDefinitionIndex, name: Symbol, loc: Loc, + source_map: Option<&SourceMap>, attributes: Vec, ) -> EnumData { let enum_def = module.enum_def_at(def_idx); - let enum_smap = self - .find_module_by_language_storage_id(&module.self_id()) - .unwrap() - .data - .source_map - .get_enum_source_map(def_idx) - .unwrap(); + let enum_smap = source_map.map(|smap| smap.get_enum_source_map(def_idx).unwrap()); let handle_idx = enum_def.enum_handle; let mut variant_data = BTreeMap::new(); for (tag, variant) in enum_def.variants.iter().enumerate() { @@ -1051,7 +1046,10 @@ impl GlobalEnv { let variant_name = self .symbol_pool .make(module.identifier_at(variant.variant_name).as_str()); - let loc = self.to_loc(&enum_smap.variants[tag].0 .1); + let loc = match enum_smap { + None => Loc::default(), + Some(smap) => self.to_loc(&smap.variants[tag].0 .1), + }; variant_data.insert( VariantId(variant_name), VariantData {