diff --git a/crates/samlang-ast/src/source.rs b/crates/samlang-ast/src/source.rs index 28d0a5b9..89c2aabc 100644 --- a/crates/samlang-ast/src/source.rs +++ b/crates/samlang-ast/src/source.rs @@ -146,19 +146,34 @@ pub mod annotation { } } + #[derive(Clone, PartialEq, Eq)] + pub struct TypeArguments { + pub location: Location, + pub start_associated_comments: CommentReference, + pub ending_associated_comments: CommentReference, + pub arguments: Vec, + } + #[derive(Clone, PartialEq, Eq)] pub struct Id { pub location: Location, pub module_reference: ModuleReference, pub id: super::Id, - pub type_arguments: Vec, + pub type_arguments: Option, + } + + #[derive(Clone, PartialEq, Eq)] + pub struct FunctionParameters { + pub location: Location, + pub ending_associated_comments: CommentReference, + pub parameters: Vec, } #[derive(Clone, PartialEq, Eq)] pub struct Function { pub location: Location, pub associated_comments: CommentReference, - pub argument_types: Vec, + pub parameters: FunctionParameters, pub return_type: Box, } @@ -189,6 +204,21 @@ pub mod annotation { } } } + + #[derive(Clone, PartialEq, Eq)] + pub struct TypeParameter { + pub loc: Location, + pub name: super::Id, + pub bound: Option, + } + + #[derive(Clone, PartialEq, Eq)] + pub struct TypeParameters { + pub location: Location, + pub start_associated_comments: CommentReference, + pub ending_associated_comments: CommentReference, + pub parameters: Vec, + } } #[derive(Clone, Copy, PartialEq, Eq)] @@ -595,13 +625,6 @@ pub mod expr { } } -#[derive(Clone, PartialEq, Eq)] -pub struct TypeParameter { - pub loc: Location, - pub name: Id, - pub bound: Option, -} - #[derive(Clone, PartialEq, Eq)] pub struct ClassMemberDeclaration { pub loc: Location, @@ -609,7 +632,7 @@ pub struct ClassMemberDeclaration { pub is_public: bool, pub is_method: bool, pub name: Id, - pub type_parameters: Rc>, + pub type_parameters: Option, pub type_: annotation::Function, pub parameters: Rc>>, } @@ -626,7 +649,7 @@ pub struct InterfaceDeclarationCommon { pub associated_comments: CommentReference, pub private: bool, pub name: Id, - pub type_parameters: Vec, + pub type_parameters: Option, /** The node after colon, interpreted as extends in interfaces and implements in classes. */ pub extends_or_implements_nodes: Vec, pub type_definition: D, @@ -714,10 +737,10 @@ impl Toplevel { } } - pub fn type_parameters(&self) -> &Vec { + pub fn type_parameters(&self) -> Option<&annotation::TypeParameters> { match self { - Toplevel::Interface(i) => &i.type_parameters, - Toplevel::Class(c) => &c.type_parameters, + Toplevel::Interface(i) => i.type_parameters.as_ref(), + Toplevel::Class(c) => c.type_parameters.as_ref(), } } @@ -760,8 +783,8 @@ pub struct Module { } pub mod test_builder { - use super::super::loc::Location; use super::*; + use super::{super::loc::Location, annotation::TypeArguments}; use samlang_heap::{ModuleReference, PStr}; pub struct CustomizedAstBuilder {} @@ -804,7 +827,7 @@ pub mod test_builder { location: Location::dummy(), module_reference: ModuleReference::ROOT, id: Id::from(PStr::STR_TYPE), - type_arguments: vec![], + type_arguments: None, }) } @@ -817,7 +840,12 @@ pub mod test_builder { location: Location::dummy(), module_reference: ModuleReference::DUMMY, id: Id::from(id), - type_arguments, + type_arguments: Some(TypeArguments { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + arguments: type_arguments, + }), } } @@ -835,13 +863,17 @@ pub mod test_builder { pub fn fn_annot_unwrapped( &self, - argument_types: Vec, + parameters: Vec, return_type: annotation::T, ) -> annotation::Function { annotation::Function { location: Location::dummy(), associated_comments: NO_COMMENT_REFERENCE, - argument_types, + parameters: annotation::FunctionParameters { + location: Location::dummy(), + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters, + }, return_type: Box::new(return_type), } } diff --git a/crates/samlang-ast/src/source_tests.rs b/crates/samlang-ast/src/source_tests.rs index df96bd61..6cb096b2 100644 --- a/crates/samlang-ast/src/source_tests.rs +++ b/crates/samlang-ast/src/source_tests.rs @@ -389,12 +389,33 @@ mod tests { annotation: Some(annotation::T::Fn(annotation::Function { location: Location::dummy(), associated_comments: NO_COMMENT_REFERENCE, - argument_types: vec![annotation::T::Id(annotation::Id { + parameters: annotation::FunctionParameters { location: Location::dummy(), - module_reference: ModuleReference::DUMMY, - id: Id::from(heap.alloc_str_for_test("name")), - type_arguments: vec![], - })], + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![ + annotation::T::Id(annotation::Id { + location: Location::dummy(), + module_reference: ModuleReference::DUMMY, + id: Id::from(heap.alloc_str_for_test("name")), + type_arguments: None, + }), + annotation::T::Id(annotation::Id { + location: Location::dummy(), + module_reference: ModuleReference::DUMMY, + id: Id::from(heap.alloc_str_for_test("name")), + type_arguments: Some(annotation::TypeArguments { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + arguments: vec![annotation::T::Primitive( + Location::dummy(), + NO_COMMENT_REFERENCE, + annotation::PrimitiveTypeKind::Any, + )], + }), + }), + ], + }, return_type: Box::new(annotation::T::Primitive( Location::dummy(), NO_COMMENT_REFERENCE, @@ -413,7 +434,7 @@ mod tests { let mut heap = Heap::new(); assert_eq!( "name", - TypeParameter { + annotation::TypeParameter { loc: Location::dummy(), name: Id::from(heap.alloc_str_for_test("name")), bound: None @@ -447,7 +468,7 @@ mod tests { associated_comments: NO_COMMENT_REFERENCE, private: false, name: Id::from(PStr::LOWER_A), - type_parameters: vec![], + type_parameters: None, extends_or_implements_nodes: vec![], type_definition: (), members: vec![ClassMemberDeclaration { @@ -456,7 +477,12 @@ mod tests { is_public: true, is_method: true, name: Id::from(PStr::LOWER_A), - type_parameters: Rc::new(vec![]), + type_parameters: Some(annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![] + }), type_: builder.fn_annot_unwrapped(vec![], builder.int_annot()), parameters: Rc::new(vec![AnnotatedId { name: Id::from(PStr::LOWER_A), @@ -467,7 +493,7 @@ mod tests { } .clone() .type_parameters - .is_empty()); + .is_none()); assert!(ModuleMembersImport { loc: Location::dummy(), imported_members: vec![], @@ -505,15 +531,28 @@ mod tests { type_: (), annotation: builder.int_annot(), })); - assert!(TypeParameter { loc: Location::dummy(), name: Id::from(PStr::LOWER_A), bound: None } - .eq(&TypeParameter { loc: Location::dummy(), name: Id::from(PStr::LOWER_A), bound: None })); + assert!(annotation::TypeParameter { + loc: Location::dummy(), + name: Id::from(PStr::LOWER_A), + bound: None + } + .eq(&annotation::TypeParameter { + loc: Location::dummy(), + name: Id::from(PStr::LOWER_A), + bound: None + })); let class = Toplevel::Class(InterfaceDeclarationCommon { loc: Location::dummy(), associated_comments: NO_COMMENT_REFERENCE, private: false, name: Id::from(heap.alloc_str_for_test("name")), - type_parameters: vec![], + type_parameters: Some(annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![], + }), extends_or_implements_nodes: vec![], type_definition: TypeDefinition::Struct { loc: Location::dummy(), @@ -530,7 +569,12 @@ mod tests { is_public: true, is_method: true, name: Id::from(PStr::LOWER_A), - type_parameters: Rc::new(vec![]), + type_parameters: Some(annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![], + }), type_: builder.fn_annot_unwrapped(vec![], builder.int_annot()), parameters: Rc::new(vec![]), }, @@ -547,7 +591,12 @@ mod tests { associated_comments: NO_COMMENT_REFERENCE, private: false, name: Id::from(heap.alloc_str_for_test("name")), - type_parameters: vec![], + type_parameters: Some(annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![], + }), extends_or_implements_nodes: vec![], type_definition: (), members: vec![ClassMemberDeclaration { @@ -556,7 +605,12 @@ mod tests { is_public: true, is_method: true, name: Id::from(PStr::LOWER_A), - type_parameters: Rc::new(vec![]), + type_parameters: Some(annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![], + }), type_: builder.fn_annot_unwrapped(vec![], builder.int_annot()), parameters: Rc::new(vec![]), }], diff --git a/crates/samlang-checker/src/global_signature.rs b/crates/samlang-checker/src/global_signature.rs index e236cf9c..400966eb 100644 --- a/crates/samlang-checker/src/global_signature.rs +++ b/crates/samlang-checker/src/global_signature.rs @@ -31,7 +31,7 @@ pub fn build_module_signature( for member in toplevel.members_iter() { let type_info = MemberSignature { is_public: member.is_public, - type_parameters: TypeParameterSignature::from_list(&member.type_parameters), + type_parameters: TypeParameterSignature::from_list(member.type_parameters.as_ref()), type_: FunctionType::from_annotation(&member.type_), }; if member.is_method { @@ -50,6 +50,7 @@ pub fn build_module_signature( type_arguments: class .type_parameters .iter() + .flat_map(|it| &it.parameters) .map(|it| Rc::new(Type::Generic(Reason::new(it.loc, Some(it.loc)), it.name.name))) .collect_vec(), })); diff --git a/crates/samlang-checker/src/main_checker.rs b/crates/samlang-checker/src/main_checker.rs index fd67a7dc..e707860d 100644 --- a/crates/samlang-checker/src/main_checker.rs +++ b/crates/samlang-checker/src/main_checker.rs @@ -1412,13 +1412,17 @@ fn check_class_member_conformance_with_signature( expected: &MemberSignature, actual: &ClassMemberDeclaration, ) { - if expected.type_parameters.len() != actual.type_parameters.len() { + let actual_type_params_len = + if let Some(tparams) = &actual.type_parameters { tparams.parameters.len() } else { 0 }; + if expected.type_parameters.len() != actual_type_params_len { let mut error = StackableError::new(); - error.add_type_params_arity_error(actual.type_parameters.len(), expected.type_parameters.len()); + error.add_type_params_arity_error(actual_type_params_len, expected.type_parameters.len()); error_set.report_stackable_error(actual.type_.location, error); } let mut has_type_parameter_conformance_errors = false; - for (e, a) in expected.type_parameters.iter().zip(actual.type_parameters.deref()) { + for (e, a) in + expected.type_parameters.iter().zip(actual.type_parameters.iter().flat_map(|it| &it.parameters)) + { if e.name != a.name.name { has_type_parameter_conformance_errors = true; } @@ -1486,6 +1490,7 @@ pub fn type_check_module( type_arguments: toplevel .type_parameters() .iter() + .flat_map(|it| &it.parameters) .map(|it| Rc::new(Type::Generic(Reason::new(it.loc, Some(it.loc)), it.name.name))) .collect_vec(), }; @@ -1543,13 +1548,13 @@ pub fn type_check_module( for member in toplevel.members_iter() { let tparam_sigs = if member.is_method { let mut sigs = TypeParameterSignature::from_list(toplevel.type_parameters()); - sigs.append(&mut TypeParameterSignature::from_list(&member.type_parameters)); + sigs.append(&mut TypeParameterSignature::from_list(member.type_parameters.as_ref())); sigs } else { if !toplevel.is_class() { error_set.report_illegal_function_in_interface(member.loc); } - TypeParameterSignature::from_list(&member.type_parameters) + TypeParameterSignature::from_list(member.type_parameters.as_ref()) }; let has_interface_def = if member.is_method { let resolved = global_signature::resolve_all_method_signatures( @@ -1580,7 +1585,7 @@ pub fn type_check_module( toplevel.name().name, tparam_sigs, ); - for tparam in member.type_parameters.iter() { + for tparam in member.type_parameters.iter().flat_map(|it| &it.parameters) { if let Some(bound) = &tparam.bound { member_cx.validate_type_instantiation_allow_abstract_types(&Type::Nominal( NominalType::from_annotation(bound), @@ -1633,11 +1638,12 @@ pub fn type_check_module( for member in &c.members { let tparam_sigs = if member.decl.is_method { let mut sigs = TypeParameterSignature::from_list(toplevel.type_parameters()); - let mut local_sigs = TypeParameterSignature::from_list(&member.decl.type_parameters); + let mut local_sigs = + TypeParameterSignature::from_list(member.decl.type_parameters.as_ref()); sigs.append(&mut local_sigs); sigs } else { - TypeParameterSignature::from_list(&member.decl.type_parameters) + TypeParameterSignature::from_list(member.decl.type_parameters.as_ref()) }; let mut cx = TypingContext::new( global_cx, diff --git a/crates/samlang-checker/src/ssa_analysis.rs b/crates/samlang-checker/src/ssa_analysis.rs index 6fa52e65..80e9cdfd 100644 --- a/crates/samlang-checker/src/ssa_analysis.rs +++ b/crates/samlang-checker/src/ssa_analysis.rs @@ -1,7 +1,7 @@ use samlang_ast::{ source::{ annotation, expr, pattern, ClassMemberDeclaration, Module, OptionallyAnnotatedId, Toplevel, - TypeDefinition, TypeParameter, + TypeDefinition, }, Location, }; @@ -113,7 +113,7 @@ impl<'a> SsaAnalysisState<'a> { { self.visit_type_parameters_with_bounds(type_parameters); for t in toplevel.extends_or_implements_nodes() { - for annot in &t.type_arguments { + for annot in t.type_arguments.iter().flat_map(|it| &it.arguments) { self.visit_annot(annot); } } @@ -159,7 +159,7 @@ impl<'a> SsaAnalysisState<'a> { if type_definition.is_some() { self.define_id(PStr::THIS, toplevel.loc()); } - for tparam in type_parameters { + for tparam in type_parameters.iter().flat_map(|it| &it.parameters) { let id = &tparam.name; self.define_id(id.name, id.loc); } @@ -199,7 +199,7 @@ impl<'a> SsaAnalysisState<'a> { body: Option<&expr::E<()>>, ) { self.context.push_scope(); - self.visit_type_parameters_with_bounds(&member.type_parameters); + self.visit_type_parameters_with_bounds(member.type_parameters.as_ref()); for param in member.parameters.iter() { self.visit_annot(¶m.annotation); } @@ -217,20 +217,31 @@ impl<'a> SsaAnalysisState<'a> { self.context.pop_scope(); } - fn visit_type_parameters_with_bounds(&mut self, type_parameters: &[TypeParameter]) { - for tparam in type_parameters { - if let Some(bound) = &tparam.bound { - self.use_id(&bound.id.name, bound.id.loc, true) + fn visit_type_parameters_with_bounds( + &mut self, + type_parameters_opt: Option<&annotation::TypeParameters>, + ) { + if let Some(annotation::TypeParameters { + location: _, + start_associated_comments: _, + ending_associated_comments: _, + parameters, + }) = type_parameters_opt + { + for tparam in parameters { + if let Some(bound) = &tparam.bound { + self.use_id(&bound.id.name, bound.id.loc, true) + } } - } - for tparam in type_parameters { - let id = &tparam.name; - self.define_id(id.name, id.loc); - } - for tparam in type_parameters { - if let Some(bound) = &tparam.bound { - for annot in &bound.type_arguments { - self.visit_annot(annot); + for tparam in parameters { + let id = &tparam.name; + self.define_id(id.name, id.loc); + } + for tparam in parameters { + if let Some(bound) = &tparam.bound { + for annot in bound.type_arguments.iter().flat_map(|it| &it.arguments) { + self.visit_annot(annot); + } } } } @@ -366,7 +377,7 @@ impl<'a> SsaAnalysisState<'a> { if self.module_reference.eq(module_reference) { self.use_id(&id.name, *location, true); } - for targ in type_arguments { + for targ in type_arguments.iter().flat_map(|it| &it.arguments) { self.visit_annot(targ); } } @@ -379,10 +390,10 @@ impl<'a> SsaAnalysisState<'a> { annotation::T::Fn(annotation::Function { location: _, associated_comments: _, - argument_types, + parameters, return_type, }) => { - for arg in argument_types { + for arg in ¶meters.parameters { self.visit_annot(arg); } self.visit_annot(return_type); diff --git a/crates/samlang-checker/src/type_.rs b/crates/samlang-checker/src/type_.rs index 7a75175e..9e436b09 100644 --- a/crates/samlang-checker/src/type_.rs +++ b/crates/samlang-checker/src/type_.rs @@ -1,9 +1,6 @@ use enum_as_inner::EnumAsInner; use itertools::Itertools; -use samlang_ast::{ - source::{annotation, TypeParameter}, - Description, Location, Reason, -}; +use samlang_ast::{source::annotation, Description, Location, Reason}; use samlang_heap::{Heap, ModuleReference, PStr}; use std::{collections::HashMap, rc::Rc}; @@ -104,11 +101,11 @@ impl NominalType { is_class_statics: false, module_reference: annotation.module_reference, id: annotation.id.name, - type_arguments: annotation - .type_arguments - .iter() - .map(|annot| Rc::new(Type::from_annotation(annot))) - .collect(), + type_arguments: if let Some(targs) = &annotation.type_arguments { + targs.arguments.iter().map(|annot| Rc::new(Type::from_annotation(annot))).collect() + } else { + Vec::with_capacity(0) + }, } } } @@ -159,7 +156,8 @@ impl FunctionType { FunctionType { reason: Reason::new(annotation.location, Some(annotation.location)), argument_types: annotation - .argument_types + .parameters + .parameters .iter() .map(|annot| Rc::new(Type::from_annotation(annot))) .collect(), @@ -289,9 +287,11 @@ pub struct TypeParameterSignature { } impl TypeParameterSignature { - pub fn from_list(type_parameters: &[TypeParameter]) -> Vec { + pub fn from_list( + type_parameters: Option<&annotation::TypeParameters>, + ) -> Vec { let mut tparam_sigs = vec![]; - for tparam in type_parameters { + for tparam in type_parameters.iter().flat_map(|it| &it.parameters) { tparam_sigs.push(TypeParameterSignature { name: tparam.name.name, bound: tparam.bound.as_ref().map(NominalType::from_annotation), @@ -654,7 +654,7 @@ pub type GlobalSignature = HashMap; mod type_tests { use super::*; use pretty_assertions::assert_eq; - use samlang_ast::source::{test_builder, Id}; + use samlang_ast::source::{test_builder, Id, NO_COMMENT_REFERENCE}; #[test] fn boilterplate() { @@ -739,25 +739,35 @@ mod type_tests { assert_eq!( "A", - TypeParameterSignature::from_list(&[TypeParameter { - loc: Location::dummy(), - name: Id::from(PStr::UPPER_A), - bound: None - }])[0] + TypeParameterSignature::from_list(Some(&annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![annotation::TypeParameter { + loc: Location::dummy(), + name: Id::from(PStr::UPPER_A), + bound: None + }] + }))[0] .pretty_print(&heap) ); assert_eq!( "A : B", - TypeParameterSignature::from_list(&[TypeParameter { - loc: Location::dummy(), - name: Id::from(PStr::UPPER_A), - bound: Some(annotation::Id { - location: Location::dummy(), - module_reference: ModuleReference::DUMMY, - id: Id::from(PStr::UPPER_B), - type_arguments: vec![] - }) - }])[0] + TypeParameterSignature::from_list(Some(&annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![annotation::TypeParameter { + loc: Location::dummy(), + name: Id::from(PStr::UPPER_A), + bound: Some(annotation::Id { + location: Location::dummy(), + module_reference: ModuleReference::DUMMY, + id: Id::from(PStr::UPPER_B), + type_arguments: None + }) + }] + }))[0] .pretty_print(&heap) ); diff --git a/crates/samlang-compiler/src/hir_lowering.rs b/crates/samlang-compiler/src/hir_lowering.rs index 6e3dea1b..ba41fc11 100644 --- a/crates/samlang-compiler/src/hir_lowering.rs +++ b/crates/samlang-compiler/src/hir_lowering.rs @@ -1146,8 +1146,8 @@ fn lower_constructors( functions } -fn lower_tparams(type_parameters: &[source::TypeParameter]) -> Vec { - type_parameters.iter().map(|it| it.name.name).collect_vec() +fn lower_tparams(type_parameters: Option<&source::annotation::TypeParameters>) -> Vec { + type_parameters.iter().flat_map(|it| &it.parameters).map(|it| it.name.name).collect_vec() } fn compile_sources_with_generics_preserved( @@ -1162,7 +1162,7 @@ fn compile_sources_with_generics_preserved( for toplevel in &source_module.toplevels { if let source::Toplevel::Class(c) = &toplevel { type_lowering_manager.generic_types = - c.type_parameters.iter().map(|it| it.name.name).collect(); + c.type_parameters.iter().flat_map(|it| &it.parameters).map(|it| it.name.name).collect(); compiled_type_defs.push(type_lowering_manager.lower_source_type_definition( heap, mod_ref, @@ -1173,7 +1173,7 @@ fn compile_sources_with_generics_preserved( && c.members.iter().any(|source::ClassMemberDefinition { decl, .. }| { decl.name.name == PStr::MAIN_FN && decl.parameters.is_empty() - && decl.type_parameters.is_empty() + && decl.type_parameters.is_none() }) { main_function_names.push(hir::FunctionName { @@ -1210,12 +1210,12 @@ fn compile_sources_with_generics_preserved( }, fn_name: member.decl.name.name, }; - let class_tparams = lower_tparams(&c.type_parameters); + let class_tparams = lower_tparams(c.type_parameters.as_ref()); if member.decl.is_method { let tparams = class_tparams .iter() .cloned() - .chain(lower_tparams(&member.decl.type_parameters)) + .chain(lower_tparams(member.decl.type_parameters.as_ref())) .collect_vec(); let tparams_set: HashSet<_> = tparams.iter().cloned().collect(); type_lowering_manager.generic_types = tparams_set; @@ -1276,7 +1276,7 @@ fn compile_sources_with_generics_preserved( compiled_functions.append(&mut compiled_functions_to_add); } else { let tparams_set: HashSet<_> = - lower_tparams(&member.decl.type_parameters).into_iter().collect(); + lower_tparams(member.decl.type_parameters.as_ref()).into_iter().collect(); let tparams = tparams_set.iter().sorted().cloned().collect_vec(); type_lowering_manager.generic_types = tparams_set; let main_function_parameter_with_types = vec![(PStr::UNDERSCORE_THIS, hir::INT_TYPE)] @@ -3164,7 +3164,7 @@ return (_t2: int);"#, associated_comments: NO_COMMENT_REFERENCE, private: false, name: source::Id::from(heap.alloc_str_for_test("I")), - type_parameters: vec![], + type_parameters: None, extends_or_implements_nodes: vec![], type_definition: (), members: vec![], @@ -3174,7 +3174,7 @@ return (_t2: int);"#, associated_comments: NO_COMMENT_REFERENCE, private: false, name: source::Id::from(PStr::MAIN_TYPE), - type_parameters: vec![], + type_parameters: None, extends_or_implements_nodes: vec![], type_definition: source::TypeDefinition::Struct { loc: Location::dummy(), @@ -3188,7 +3188,7 @@ return (_t2: int);"#, is_public: true, is_method: false, name: source::Id::from(PStr::MAIN_FN), - type_parameters: Rc::new(vec![]), + type_parameters: None, type_: annot_builder.fn_annot_unwrapped(vec![], annot_builder.unit_annot()), parameters: Rc::new(vec![]), }, @@ -3225,11 +3225,16 @@ return (_t2: int);"#, is_public: true, is_method: false, name: source::Id::from(heap.alloc_str_for_test("loopy")), - type_parameters: Rc::new(vec![source::TypeParameter { - loc: Location::dummy(), - name: source::Id::from(heap.alloc_str_for_test("T")), - bound: None, - }]), + type_parameters: Some(source::annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![source::annotation::TypeParameter { + loc: Location::dummy(), + name: source::Id::from(heap.alloc_str_for_test("T")), + bound: None, + }], + }), type_: annot_builder.fn_annot_unwrapped(vec![], annot_builder.unit_annot()), parameters: Rc::new(vec![]), }, @@ -3266,7 +3271,7 @@ return (_t2: int);"#, associated_comments: NO_COMMENT_REFERENCE, private: false, name: source::Id::from(heap.alloc_str_for_test("Class1")), - type_parameters: vec![], + type_parameters: None, extends_or_implements_nodes: vec![], type_definition: source::TypeDefinition::Struct { loc: Location::dummy(), @@ -3284,7 +3289,7 @@ return (_t2: int);"#, is_public: true, is_method: true, name: source::Id::from(heap.alloc_str_for_test("foo")), - type_parameters: Rc::new(vec![]), + type_parameters: None, type_: annot_builder .fn_annot_unwrapped(vec![annot_builder.int_annot()], annot_builder.int_annot()), parameters: Rc::new(vec![source::AnnotatedId { @@ -3302,7 +3307,7 @@ return (_t2: int);"#, is_public: true, is_method: false, name: source::Id::from(heap.alloc_str_for_test("infiniteLoop")), - type_parameters: Rc::new(vec![]), + type_parameters: None, type_: annot_builder.fn_annot_unwrapped(vec![], annot_builder.unit_annot()), parameters: Rc::new(vec![]), }, @@ -3339,7 +3344,7 @@ return (_t2: int);"#, is_public: true, is_method: false, name: source::Id::from(heap.alloc_str_for_test("factorial")), - type_parameters: Rc::new(vec![]), + type_parameters: None, type_: annot_builder.fn_annot_unwrapped( vec![annot_builder.int_annot(), annot_builder.int_annot()], annot_builder.int_annot(), @@ -3428,7 +3433,7 @@ return (_t2: int);"#, associated_comments: NO_COMMENT_REFERENCE, private: false, name: source::Id::from(heap.alloc_str_for_test("Class2")), - type_parameters: vec![], + type_parameters: None, extends_or_implements_nodes: vec![], type_definition: source::TypeDefinition::Enum { loc: Location::dummy(), @@ -3444,11 +3449,16 @@ return (_t2: int);"#, associated_comments: NO_COMMENT_REFERENCE, private: false, name: source::Id::from(heap.alloc_str_for_test("Class3")), - type_parameters: vec![source::TypeParameter { - loc: Location::dummy(), - name: source::Id::from(heap.alloc_str_for_test("T")), - bound: None, - }], + type_parameters: Some(source::annotation::TypeParameters { + location: Location::dummy(), + start_associated_comments: NO_COMMENT_REFERENCE, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: vec![source::annotation::TypeParameter { + loc: Location::dummy(), + name: source::Id::from(heap.alloc_str_for_test("T")), + bound: None, + }], + }), extends_or_implements_nodes: vec![], type_definition: source::TypeDefinition::Struct { loc: Location::dummy(), diff --git a/crates/samlang-parser/src/source_parser.rs b/crates/samlang-parser/src/source_parser.rs index ea8394b7..96aae9d4 100644 --- a/crates/samlang-parser/src/source_parser.rs +++ b/crates/samlang-parser/src/source_parser.rs @@ -233,7 +233,11 @@ impl<'a> SourceParser<'a> { } fn parse_upper_id(&mut self) -> Id { - let associated_comments = self.collect_preceding_comments(); + self.parse_upper_id_with_comments(vec![]) + } + + fn parse_upper_id_with_comments(&mut self, mut associated_comments: Vec) -> Id { + associated_comments.append(&mut self.collect_preceding_comments()); let (loc, name) = self.assert_and_peek_upper_id(); Id { loc, @@ -394,20 +398,8 @@ mod toplevel_parser { }; let name = parser.parse_upper_id(); loc = loc.union(&name.loc); - let (type_param_loc_start, type_param_loc_end, mut type_parameters) = - if let Token(loc_start, TokenContent::Operator(TokenOp::LT)) = parser.peek() { - parser.consume(); - let type_params = parser.parse_comma_separated_list_with_end_token( - TokenOp::GT, - &mut super::type_parser::parse_type_parameter, - ); - let loc_end = parser.assert_and_consume_operator(TokenOp::GT); - (Some(loc_start), Some(loc_end), type_params) - } else { - (None, None, vec![]) - }; - parser.available_tparams = type_parameters.iter().map(|it| it.name.name).collect(); - super::type_parser::fix_tparams_with_generic_annot(parser, &mut type_parameters); + parser.available_tparams = HashSet::new(); + let type_parameters = super::type_parser::parse_type_parameters(parser); let (type_definition, extends_or_implements_nodes) = match parser.peek().1 { TokenContent::Operator(TokenOp::LBRACE | TokenOp::COLON) | TokenContent::Keyword(Keyword::CLASS | Keyword::INTERFACE | Keyword::PRIVATE) => { @@ -419,7 +411,11 @@ mod toplevel_parser { } else { vec![] }; - loc = if let Some(loc_end) = type_param_loc_end { loc.union(&loc_end) } else { loc }; + loc = if let Some(tparams_node) = &type_parameters { + loc.union(&tparams_node.location) + } else { + loc + }; let type_def = TypeDefinition::Struct { loc: parser.peek().0, fields: vec![] }; (type_def, nodes) } @@ -427,8 +423,11 @@ mod toplevel_parser { let type_def_loc_start = parser.assert_and_consume_operator(TokenOp::LPAREN); let mut type_def = parse_type_definition_inner(parser); let type_def_loc_end = parser.assert_and_consume_operator(TokenOp::RPAREN); - let type_def_loc = - type_param_loc_start.unwrap_or(type_def_loc_start).union(&type_def_loc_end); + let type_def_loc = type_parameters + .as_ref() + .map(|it| it.location) + .unwrap_or(type_def_loc_start) + .union(&type_def_loc_end); match &mut type_def { TypeDefinition::Struct { loc, fields: _ } => *loc = type_def_loc, TypeDefinition::Enum { loc, variants: _ } => *loc = type_def_loc, @@ -480,19 +479,8 @@ mod toplevel_parser { (parser.assert_and_consume_keyword(Keyword::INTERFACE), false) }; let name = parser.parse_upper_id(); - let mut type_parameters = if let TokenContent::Operator(TokenOp::LT) = parser.peek().1 { - parser.consume(); - let type_params = parser.parse_comma_separated_list_with_end_token( - TokenOp::GT, - &mut super::type_parser::parse_type_parameter, - ); - loc = loc.union(&parser.assert_and_consume_operator(TokenOp::GT)); - type_params - } else { - vec![] - }; - parser.available_tparams = type_parameters.iter().map(|it| it.name.name).collect(); - super::type_parser::fix_tparams_with_generic_annot(parser, &mut type_parameters); + parser.available_tparams = HashSet::new(); + let type_parameters = super::type_parser::parse_type_parameters(parser); let extends_or_implements_nodes = if let TokenContent::Operator(TokenOp::COLON) = parser.peek().1 { parser.consume(); @@ -567,8 +555,7 @@ mod toplevel_parser { } parser.assert_and_consume_keyword(Keyword::VAL); let name = parser.parse_lower_id(); - parser.assert_and_consume_operator(TokenOp::COLON); - let annotation = super::type_parser::parse_annotation(parser); + let annotation = super::type_parser::parse_annotation_with_colon(parser); FieldDefinition { name, annotation, is_public } } @@ -644,19 +631,10 @@ mod toplevel_parser { if !is_method { parser.available_tparams = HashSet::new(); } - let mut type_parameters = if let TokenContent::Operator(TokenOp::LT) = parser.peek().1 { - parser.consume(); - let type_params = parser.parse_comma_separated_list_with_end_token( - TokenOp::GT, - &mut super::type_parser::parse_type_parameter, - ); - parser.assert_and_consume_operator(TokenOp::GT); - type_params - } else { - vec![] - }; - parser.available_tparams.extend(type_parameters.iter().map(|it| it.name.name)); - super::type_parser::fix_tparams_with_generic_annot(parser, &mut type_parameters); + let type_parameters = super::type_parser::parse_type_parameters(parser); + parser + .available_tparams + .extend(type_parameters.iter().flat_map(|it| &it.parameters).map(|it| it.name.name)); let name = parser.parse_lower_id(); let fun_type_loc_start = parser.assert_and_consume_operator(TokenOp::LPAREN); let parameters = if let TokenContent::Operator(TokenOp::RPAREN) = parser.peek().1 { @@ -668,8 +646,7 @@ mod toplevel_parser { ) }; parser.assert_and_consume_operator(TokenOp::RPAREN); - parser.assert_and_consume_operator(TokenOp::COLON); - let return_type = super::type_parser::parse_annotation(parser); + let return_type = super::type_parser::parse_annotation_with_colon(parser); let fun_type_loc = fun_type_loc_start.union(&return_type.location()); ClassMemberDeclaration { loc: start_loc.union(&fun_type_loc), @@ -677,11 +654,15 @@ mod toplevel_parser { is_public, is_method, name, - type_parameters: std::rc::Rc::new(type_parameters), + type_parameters, type_: annotation::Function { location: fun_type_loc, associated_comments: NO_COMMENT_REFERENCE, - argument_types: parameters.iter().map(|it| it.annotation.clone()).collect_vec(), + parameters: annotation::FunctionParameters { + location: fun_type_loc, + ending_associated_comments: NO_COMMENT_REFERENCE, + parameters: parameters.iter().map(|it| it.annotation.clone()).collect_vec(), + }, return_type: Box::new(return_type), }, parameters: std::rc::Rc::new(parameters), @@ -1511,19 +1492,20 @@ mod expression_parser { pub(super) fn parse_statement( parser: &mut super::SourceParser, ) -> expr::DeclarationStatement<()> { - let concrete_comments = parser.collect_preceding_comments(); - let associated_comments = parser.comments_store.create_comment_reference(concrete_comments); + let mut concrete_comments = parser.collect_preceding_comments(); let start_loc = parser.assert_and_consume_keyword(Keyword::LET); let pattern = super::pattern_parser::parse_matching_pattern(parser); let annotation = if let Token(_, TokenContent::Operator(TokenOp::COLON)) = parser.peek() { - parser.consume(); - Some(super::type_parser::parse_annotation(parser)) + Some(super::type_parser::parse_annotation_with_colon(parser)) } else { None }; + concrete_comments.append(&mut parser.collect_preceding_comments()); parser.assert_and_consume_operator(TokenOp::ASSIGN); let assigned_expression = Box::new(parse_expression(parser)); + concrete_comments.append(&mut parser.collect_preceding_comments()); let loc = start_loc.union(&parser.assert_and_consume_operator(TokenOp::SEMICOLON)); + let associated_comments = parser.comments_store.create_comment_reference(concrete_comments); expr::DeclarationStatement { loc, associated_comments, @@ -1630,24 +1612,51 @@ mod type_parser { use super::super::lexer::{Keyword, Token, TokenContent, TokenOp}; use samlang_ast::source::*; - pub(super) fn parse_type_parameter(parser: &mut super::SourceParser) -> TypeParameter { + pub(super) fn parse_type_parameters( + parser: &mut super::SourceParser, + ) -> Option { + if let Token(start_loc, TokenContent::Operator(TokenOp::LT)) = parser.peek() { + let start_comments = parser.collect_preceding_comments(); + parser.consume(); + let mut parameters = parser.parse_comma_separated_list_with_end_token( + TokenOp::GT, + &mut super::type_parser::parse_type_parameter, + ); + let end_comments = parser.collect_preceding_comments(); + let location = start_loc.union(&parser.assert_and_consume_operator(TokenOp::GT)); + parser.available_tparams.extend(parameters.iter().map(|it| it.name.name)); + fix_tparams_with_generic_annot(parser, &mut parameters); + Some(annotation::TypeParameters { + location, + start_associated_comments: parser.comments_store.create_comment_reference(start_comments), + ending_associated_comments: parser.comments_store.create_comment_reference(end_comments), + parameters, + }) + } else { + None + } + } + + pub(super) fn parse_type_parameter( + parser: &mut super::SourceParser, + ) -> annotation::TypeParameter { let name = &parser.parse_upper_id(); let (bound, loc) = if let Token(_, TokenContent::Operator(TokenOp::COLON)) = parser.peek() { + let id_comments = parser.collect_preceding_comments(); parser.consume(); - let id = parser.parse_upper_id(); + let id = parser.parse_upper_id_with_comments(id_comments); let bound = super::type_parser::parse_identifier_annot(parser, id); let loc = name.loc.union(&bound.location); (Some(bound), loc) } else { (None, name.loc) }; - TypeParameter { loc, name: *name, bound } + annotation::TypeParameter { loc, name: *name, bound } } pub(super) fn parse_annotated_id(parser: &mut super::SourceParser) -> AnnotatedId<()> { let name = parser.parse_lower_id(); - parser.assert_and_consume_operator(TokenOp::COLON); - let annotation = parse_annotation(parser); + let annotation = parse_annotation_with_colon(parser); AnnotatedId { name, type_: (), annotation } } @@ -1661,15 +1670,27 @@ mod type_parser { fn parse_optional_annotation(parser: &mut super::SourceParser) -> Option { if let Token(_, TokenContent::Operator(TokenOp::COLON)) = parser.peek() { - parser.consume(); - Some(parse_annotation(parser)) + Some(parse_annotation_with_colon(parser)) } else { None } } pub(super) fn parse_annotation(parser: &mut super::SourceParser) -> annotation::T { - let associated_comments = parser.collect_preceding_comments(); + parse_annotation_with_additional_comments(parser, vec![]) + } + + pub(super) fn parse_annotation_with_colon(parser: &mut super::SourceParser) -> annotation::T { + let annotation_comments = parser.collect_preceding_comments(); + parser.assert_and_consume_operator(TokenOp::COLON); + parse_annotation_with_additional_comments(parser, annotation_comments) + } + + pub(super) fn parse_annotation_with_additional_comments( + parser: &mut super::SourceParser, + mut associated_comments: Vec, + ) -> annotation::T { + associated_comments.append(&mut parser.collect_preceding_comments()); let peeked = parser.peek(); match peeked.1 { TokenContent::Keyword(Keyword::UNIT) => { @@ -1701,8 +1722,7 @@ mod type_parser { let associated_comments = parser.comments_store.create_comment_reference(vec![]); let id_annot = parse_identifier_annot(parser, Id { loc: peeked.0, associated_comments, name }); - if id_annot.type_arguments.is_empty() - && parser.available_tparams.contains(&id_annot.id.name) + if id_annot.type_arguments.is_none() && parser.available_tparams.contains(&id_annot.id.name) { annotation::T::Generic(id_annot.location, id_annot.id) } else { @@ -1711,23 +1731,35 @@ mod type_parser { } TokenContent::Operator(TokenOp::LPAREN) => { parser.consume(); - let argument_types = - if let Token(_, TokenContent::Operator(TokenOp::RPAREN)) = parser.peek() { - parser.consume(); - vec![] - } else { - let types = parser - .parse_comma_separated_list_with_end_token(TokenOp::RPAREN, &mut parse_annotation); - parser.assert_and_consume_operator(TokenOp::RPAREN); - types - }; - parser.assert_and_consume_operator(TokenOp::ARROW); + let parameters = if let Token(_, TokenContent::Operator(TokenOp::RPAREN)) = parser.peek() { + let mut comments = parser.collect_preceding_comments(); + let location = peeked.0.union(&parser.assert_and_consume_operator(TokenOp::RPAREN)); + comments.append(&mut parser.collect_preceding_comments()); + parser.assert_and_consume_operator(TokenOp::ARROW); + annotation::FunctionParameters { + location, + ending_associated_comments: parser.comments_store.create_comment_reference(comments), + parameters: Vec::with_capacity(0), + } + } else { + let parameters = parser + .parse_comma_separated_list_with_end_token(TokenOp::RPAREN, &mut parse_annotation); + let mut comments = parser.collect_preceding_comments(); + let location = peeked.0.union(&parser.assert_and_consume_operator(TokenOp::RPAREN)); + comments.append(&mut parser.collect_preceding_comments()); + parser.assert_and_consume_operator(TokenOp::ARROW); + annotation::FunctionParameters { + location, + ending_associated_comments: parser.comments_store.create_comment_reference(comments), + parameters, + } + }; let return_type = parse_annotation(parser); let location = peeked.0.union(&return_type.location()); annotation::T::Fn(annotation::Function { location, associated_comments: parser.comments_store.create_comment_reference(associated_comments), - argument_types, + parameters, return_type: Box::new(return_type), }) } @@ -1747,12 +1779,14 @@ mod type_parser { pub(super) fn fix_tparams_with_generic_annot( parser: &mut super::SourceParser, - tparams: &mut [TypeParameter], + tparams: &mut [annotation::TypeParameter], ) { for tparam in tparams { if let Some(bound) = &mut tparam.bound { - for annot in &mut bound.type_arguments { - fix_annot_with_generic_annot(parser, annot); + if let Some(targs) = &mut bound.type_arguments { + for annot in &mut targs.arguments { + fix_annot_with_generic_annot(parser, annot); + } } } } @@ -1765,14 +1799,13 @@ mod type_parser { match annot { annotation::T::Primitive(_, _, _) | annotation::T::Generic(_, _) => {} annotation::T::Id(id_annot) => { - if id_annot.type_arguments.is_empty() - && parser.available_tparams.contains(&id_annot.id.name) + if id_annot.type_arguments.is_none() && parser.available_tparams.contains(&id_annot.id.name) { *annot = annotation::T::Generic(id_annot.location, id_annot.id) } } annotation::T::Fn(t) => { - for annot in &mut t.argument_types { + for annot in &mut t.parameters.parameters { fix_annot_with_generic_annot(parser, annot); } fix_annot_with_generic_annot(parser, &mut t.return_type); @@ -1784,16 +1817,34 @@ mod type_parser { parser: &mut super::SourceParser, identifier: Id, ) -> annotation::Id { - let (type_arguments, location) = - if let Token(_, TokenContent::Operator(TokenOp::LT)) = parser.peek() { + let type_arguments = + if let Token(start_loc, TokenContent::Operator(TokenOp::LT)) = parser.peek() { + let start_associated_comments = { + let comments = parser.collect_preceding_comments(); + parser.comments_store.create_comment_reference(comments) + }; parser.consume(); - let types = + let arguments = parser.parse_comma_separated_list_with_end_token(TokenOp::GT, &mut parse_annotation); - let location = identifier.loc.union(&parser.assert_and_consume_operator(TokenOp::GT)); - (types, location) + let ending_associated_comments = { + let comments = parser.collect_preceding_comments(); + parser.comments_store.create_comment_reference(comments) + }; + let location = start_loc.union(&parser.assert_and_consume_operator(TokenOp::GT)); + Some(annotation::TypeArguments { + location, + start_associated_comments, + ending_associated_comments, + arguments, + }) } else { - (vec![], identifier.loc) + None }; + let location = if let Some(node) = &type_arguments { + identifier.loc.union(&node.location) + } else { + identifier.loc + }; annotation::Id { location, module_reference: super::utils::resolve_class(parser, identifier.name), diff --git a/crates/samlang-printer/src/lib.rs b/crates/samlang-printer/src/lib.rs index 0d52582c..b5e860c6 100644 --- a/crates/samlang-printer/src/lib.rs +++ b/crates/samlang-printer/src/lib.rs @@ -40,11 +40,17 @@ pub fn pretty_print_statement( pub fn pretty_print_import( heap: &samlang_heap::Heap, available_width: usize, + comment_store: &samlang_ast::source::CommentStore, import: &samlang_ast::source::ModuleMembersImport, ) -> String { prettier::pretty_print( available_width, - source_printer::import_to_document(heap, import.imported_module, &import.imported_members), + source_printer::import_to_document( + heap, + comment_store, + import.imported_module, + &import.imported_members, + ), ) } diff --git a/crates/samlang-printer/src/prettier.rs b/crates/samlang-printer/src/prettier.rs index 5383a14e..58796d8d 100644 --- a/crates/samlang-printer/src/prettier.rs +++ b/crates/samlang-printer/src/prettier.rs @@ -174,7 +174,7 @@ impl Document { /// Each variant can be translated easily into a printable form without extra state. enum IntermediateDocumentTokenForPrinting { Text(Str), - Line(usize), + Line { indentation: usize, hard: bool }, } enum DocumentList { @@ -216,7 +216,10 @@ fn generate_best_doc( list = rest.clone(); } Document::Line | Document::LineFlattenToNil | Document::LineHard => { - collector.push(IntermediateDocumentTokenForPrinting::Line(*indentation)); + collector.push(IntermediateDocumentTokenForPrinting::Line { + indentation: *indentation, + hard: document.as_ref().eq(&Document::LineHard), + }); consumed = *indentation; enforce_consumed = false; list = rest.clone(); @@ -253,13 +256,21 @@ pub(super) fn pretty_print(available_width: usize, document: Document) -> String ); let mut string_builder = String::new(); + let mut prev_hard_line = false; for token in collector { match token { IntermediateDocumentTokenForPrinting::Text(s) => { string_builder.push_str(&s); + prev_hard_line = false; } - IntermediateDocumentTokenForPrinting::Line(indentation) => { + IntermediateDocumentTokenForPrinting::Line { indentation, hard } => { + if !hard && prev_hard_line { + // If we already printed a hard line before and we are getting a soft line, + // undo the hardline first. + string_builder.truncate(string_builder.trim_end().len()); + } string_builder.push('\n'); + prev_hard_line = hard; for _ in 0..indentation { string_builder.push(' '); } diff --git a/crates/samlang-printer/src/source_printer.rs b/crates/samlang-printer/src/source_printer.rs index 672bc9db..2fdd7461 100644 --- a/crates/samlang-printer/src/source_printer.rs +++ b/crates/samlang-printer/src/source_printer.rs @@ -3,7 +3,7 @@ use itertools::Itertools; use samlang_ast::source::{ annotation, expr, pattern, ClassDefinition, ClassMemberDeclaration, CommentKind, CommentReference, CommentStore, Id, InterfaceDeclaration, Module, Toplevel, TypeDefinition, - TypeParameter, + NO_COMMENT_REFERENCE, }; use samlang_heap::{Heap, ModuleReference, PStr}; use std::{collections::HashMap, ops::Deref, rc::Rc}; @@ -12,19 +12,6 @@ fn rc_pstr(heap: &Heap, s: PStr) -> Str { rc_string(String::from(s.as_str(heap))) } -fn comma_sep_list Document>(elements: &[E], doc_creator: F) -> Document { - let mut iter = elements.iter().rev(); - if let Some(last) = iter.next() { - let mut base = doc_creator(last); - for e in iter { - base = Document::concat(vec![doc_creator(e), Document::Text(rcs(",")), Document::Line, base]); - } - base - } else { - Document::Nil - } -} - fn parenthesis_surrounded_doc(doc: Document) -> Document { Document::no_space_bracket(rcs("("), doc, rcs(")")) } @@ -120,6 +107,41 @@ fn create_opt_preceding_comment_doc( } } +fn comma_sep_list Document>( + heap: &Heap, + comment_store: &CommentStore, + elements: &[E], + ending_comments: CommentReference, + doc_creator: F, +) -> Document { + let comment_doc_opt = associated_comments_doc( + heap, + comment_store, + ending_comments, + DocumentGrouping::Expanded, + false, + ); + let mut iter = elements.iter().rev(); + let base = if let Some(last) = iter.next() { + let mut base = doc_creator(last); + for e in iter { + base = Document::concat(vec![doc_creator(e), Document::Text(rcs(",")), Document::Line, base]); + } + base + } else { + Document::Nil + }; + if let Some(comment_doc) = comment_doc_opt { + if matches!(base, Document::Nil) { + comment_doc + } else { + Document::concat(vec![base, Document::Text(rcs(",")), Document::Line, comment_doc]) + } + } else { + base + } +} + pub(super) fn annotation_to_doc( heap: &Heap, comment_store: &CommentStore, @@ -142,16 +164,20 @@ pub(super) fn annotation_to_doc( annotation::T::Fn(annotation::Function { location: _, associated_comments, - argument_types, + parameters, return_type, }) => create_opt_preceding_comment_doc( heap, comment_store, *associated_comments, Document::concat(vec![ - parenthesis_surrounded_doc(comma_sep_list(argument_types, |annot| { - annotation_to_doc(heap, comment_store, annot) - })), + parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + ¶meters.parameters, + parameters.ending_associated_comments, + |annot| annotation_to_doc(heap, comment_store, annot), + )), Document::Text(rcs(" -> ")), annotation_to_doc(heap, comment_store, return_type), ]), @@ -167,9 +193,13 @@ fn optional_targs( if type_args.is_empty() { Document::Nil } else { - angle_bracket_surrounded_doc(comma_sep_list(type_args, |a| { - annotation_to_doc(heap, comment_store, a) - })) + angle_bracket_surrounded_doc(comma_sep_list( + heap, + comment_store, + type_args, + NO_COMMENT_REFERENCE, + |a| annotation_to_doc(heap, comment_store, a), + )) } } @@ -182,15 +212,24 @@ fn id_annot_to_doc( heap, comment_store, id.associated_comments, - if type_arguments.is_empty() { - Document::Text(rc_pstr(heap, id.name)) - } else { + if let Some(targs) = type_arguments { Document::Concat( Rc::new(Document::Text(rc_pstr(heap, id.name))), - Rc::new(angle_bracket_surrounded_doc(comma_sep_list(type_arguments, |annot| { - annotation_to_doc(heap, comment_store, annot) - }))), + Rc::new(create_opt_preceding_comment_doc( + heap, + comment_store, + targs.start_associated_comments, + angle_bracket_surrounded_doc(comma_sep_list( + heap, + comment_store, + &targs.arguments, + targs.ending_associated_comments, + |annot| annotation_to_doc(heap, comment_store, annot), + )), + )), ) + } else { + Document::Text(rc_pstr(heap, id.name)) }, ) } @@ -279,7 +318,7 @@ fn add_if_else_first_half_docs( expr::IfElseCondition::Expression(e) => documents.push(create_doc(heap, comment_store, e)), expr::IfElseCondition::Guard(p, e) => { documents.push(Document::Text(rcs("let "))); - documents.push(matching_pattern_to_document(heap, p)); + documents.push(matching_pattern_to_document(heap, comment_store, p)); documents.push(Document::Text(rcs(" = "))); documents.push(create_doc(heap, comment_store, e)); } @@ -449,9 +488,13 @@ fn create_chainable_ir_docs( (base, chain) } expr::E::Call(e) => { - let args_doc = parenthesis_surrounded_doc(comma_sep_list(&e.arguments, |e| { - create_doc(heap, comment_store, e) - })); + let args_doc = parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + &e.arguments, + NO_COMMENT_REFERENCE, + |e| create_doc(heap, comment_store, e), + )); let (mut base, mut chain) = create_chainable_ir_docs(heap, comment_store, expression, &e.callee); if let Some((_, last_docs)) = chain.last_mut() { @@ -482,11 +525,13 @@ fn create_doc_without_preceding_comment( match expression { expr::E::Literal(_, l) => Document::Text(rc_string(l.pretty_print(heap))), expr::E::LocalId(_, id) | expr::E::ClassId(_, _, id) => Document::Text(rc_pstr(heap, id.name)), - expr::E::Tuple(_, expressions) => { - parenthesis_surrounded_doc(comma_sep_list(expressions, |e| { - create_doc(heap, comment_store, e) - })) - } + expr::E::Tuple(_, expressions) => parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + expressions, + NO_COMMENT_REFERENCE, + |e| create_doc(heap, comment_store, e), + )), expr::E::FieldAccess(_) | expr::E::MethodAccess(_) | expr::E::Call(_) => { create_doc_for_dotted_chain( heap, @@ -578,7 +623,7 @@ fn create_doc_without_preceding_comment( expr::E::Match(e) => { let mut list = vec![]; for case in &e.cases { - list.push(matching_pattern_to_document(heap, &case.pattern)); + list.push(matching_pattern_to_document(heap, comment_store, &case.pattern)); list.push(Document::Text(rcs(" -> "))); list.push(create_doc(heap, comment_store, &case.body)); list.push(Document::Text(rcs(","))); @@ -595,21 +640,27 @@ fn create_doc_without_preceding_comment( } expr::E::Lambda(e) => Document::concat(vec![ - parenthesis_surrounded_doc(comma_sep_list(&e.parameters, |id| { - create_opt_preceding_comment_doc( - heap, - comment_store, - id.name.associated_comments, - if let Some(annot) = &id.annotation { - Document::Concat( - Rc::new(Document::Text(rc_string(format!("{}: ", id.name.name.as_str(heap),)))), - Rc::new(annotation_to_doc(heap, comment_store, annot)), - ) - } else { - Document::Text(rc_pstr(heap, id.name.name)) - }, - ) - })), + parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + &e.parameters, + NO_COMMENT_REFERENCE, + |id| { + create_opt_preceding_comment_doc( + heap, + comment_store, + id.name.associated_comments, + if let Some(annot) = &id.annotation { + Document::Concat( + Rc::new(Document::Text(rc_string(format!("{}: ", id.name.name.as_str(heap),)))), + Rc::new(annotation_to_doc(heap, comment_store, annot)), + ) + } else { + Document::Text(rc_pstr(heap, id.name.name)) + }, + ) + }, + )), Document::Text(rcs(" -> ")), create_doc_for_subexpression_considering_precedence_level( heap, @@ -651,26 +702,36 @@ fn create_doc(heap: &Heap, comment_store: &CommentStore, expression: &expr::E<() ) } -fn matching_pattern_to_document(heap: &Heap, pattern: &pattern::MatchingPattern<()>) -> Document { +fn matching_pattern_to_document( + heap: &Heap, + comment_store: &CommentStore, + pattern: &pattern::MatchingPattern<()>, +) -> Document { match pattern { - pattern::MatchingPattern::Tuple(_, names) => { - parenthesis_surrounded_doc(comma_sep_list(names, |it| { - matching_pattern_to_document(heap, &it.pattern) - })) - } - pattern::MatchingPattern::Object(_, names) => { - braces_surrounded_doc(comma_sep_list(names, |it| { + pattern::MatchingPattern::Tuple(_, names) => parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + names, + NO_COMMENT_REFERENCE, + |it| matching_pattern_to_document(heap, comment_store, &it.pattern), + )), + pattern::MatchingPattern::Object(_, names) => braces_surrounded_doc(comma_sep_list( + heap, + comment_store, + names, + NO_COMMENT_REFERENCE, + |it| { if it.shorthand { Document::Text(rc_pstr(heap, it.field_name.name)) } else { Document::concat(vec![ Document::Text(rc_pstr(heap, it.field_name.name)), Document::Text(rcs(" as ")), - matching_pattern_to_document(heap, &it.pattern), + matching_pattern_to_document(heap, comment_store, &it.pattern), ]) } - })) - } + }, + )), pattern::MatchingPattern::Variant(pattern::VariantPattern { loc: _, tag_order: _, @@ -683,9 +744,13 @@ fn matching_pattern_to_document(heap: &Heap, pattern: &pattern::MatchingPattern< } else { Document::concat(vec![ Document::Text(rc_pstr(heap, tag.name)), - parenthesis_surrounded_doc(comma_sep_list(data_variables, |(p, _)| { - matching_pattern_to_document(heap, p) - })), + parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + data_variables, + NO_COMMENT_REFERENCE, + |(p, _)| matching_pattern_to_document(heap, comment_store, p), + )), ]) } } @@ -700,7 +765,7 @@ pub(super) fn statement_to_document( stmt: &expr::DeclarationStatement<()>, ) -> Document { let mut segments = vec![]; - let pattern_doc = matching_pattern_to_document(heap, &stmt.pattern); + let pattern_doc = matching_pattern_to_document(heap, comment_store, &stmt.pattern); segments.push( associated_comments_doc( heap, @@ -731,32 +796,38 @@ fn type_parameters_to_doc( heap: &Heap, comment_store: &CommentStore, extra_space: bool, - tparams: &Vec, + tparams_opt: Option<&annotation::TypeParameters>, ) -> Document { - if tparams.is_empty() { - Document::Nil - } else { - let doc = angle_bracket_surrounded_doc(comma_sep_list(tparams, |tparam| { - create_opt_preceding_comment_doc( - heap, - comment_store, - tparam.name.associated_comments, - if let Some(b) = &tparam.bound { - Document::concat(vec![ - Document::Text(rc_pstr(heap, tparam.name.name)), - Document::Text(rcs(": ")), - id_annot_to_doc(heap, comment_store, b), - ]) - } else { - Document::Text(rc_pstr(heap, tparam.name.name)) - }, - ) - })); + if let Some(tparams) = tparams_opt { + let doc = angle_bracket_surrounded_doc(comma_sep_list( + heap, + comment_store, + &tparams.parameters, + NO_COMMENT_REFERENCE, + |tparam| { + create_opt_preceding_comment_doc( + heap, + comment_store, + tparam.name.associated_comments, + if let Some(b) = &tparam.bound { + Document::concat(vec![ + Document::Text(rc_pstr(heap, tparam.name.name)), + Document::Text(rcs(": ")), + id_annot_to_doc(heap, comment_store, b), + ]) + } else { + Document::Text(rc_pstr(heap, tparam.name.name)) + }, + ) + }, + )); if extra_space { Document::Concat(Rc::new(doc), Rc::new(Document::Text(rcs(" ")))) } else { doc } + } else { + Document::Nil } } @@ -800,20 +871,26 @@ fn create_doc_for_interface_member( .unwrap_or(Document::Nil), if member.is_public { Document::Nil } else { Document::Text(rcs("private ")) }, Document::Text(rcs(if member.is_method { "method " } else { "function " })), - type_parameters_to_doc(heap, comment_store, true, &member.type_parameters), + type_parameters_to_doc(heap, comment_store, true, member.type_parameters.as_ref()), Document::Text(rc_pstr(heap, member.name.name)), - parenthesis_surrounded_doc(comma_sep_list(member.parameters.as_ref(), |param| { - create_opt_preceding_comment_doc( - heap, - comment_store, - param.name.associated_comments, - Document::concat(vec![ - Document::Text(rc_pstr(heap, param.name.name)), - Document::Text(rcs(": ")), - annotation_to_doc(heap, comment_store, ¶m.annotation), - ]), - ) - })), + parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + member.parameters.as_ref(), + NO_COMMENT_REFERENCE, + |param| { + create_opt_preceding_comment_doc( + heap, + comment_store, + param.name.associated_comments, + Document::concat(vec![ + Document::Text(rc_pstr(heap, param.name.name)), + Document::Text(rcs(": ")), + annotation_to_doc(heap, comment_store, ¶m.annotation), + ]), + ) + }, + )), Document::Text(rcs(": ")), annotation_to_doc(heap, comment_store, &member.type_.return_type), if body.is_none() { Document::Nil } else { Document::Text(rcs(" =")) }, @@ -864,7 +941,7 @@ fn interface_to_doc( if interface.private { "private " } else { "" }, interface.name.name.as_str(heap) ))), - type_parameters_to_doc(heap, comment_store, false, &interface.type_parameters), + type_parameters_to_doc(heap, comment_store, false, interface.type_parameters.as_ref()), extends_or_implements_node_to_doc(heap, comment_store, &interface.extends_or_implements_nodes), ]; @@ -905,11 +982,15 @@ fn class_to_doc( if class.private { "private " } else { "" }, class.name.name.as_str(heap) ))), - type_parameters_to_doc(heap, comment_store, false, &class.type_parameters), + type_parameters_to_doc(heap, comment_store, false, class.type_parameters.as_ref()), match &class.type_definition { TypeDefinition::Struct { loc: _, fields } if fields.is_empty() => Document::Nil, - TypeDefinition::Struct { loc: _, fields } => { - parenthesis_surrounded_doc(comma_sep_list(fields, |field| { + TypeDefinition::Struct { loc: _, fields } => parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + fields, + NO_COMMENT_REFERENCE, + |field| { Document::Concat( Rc::new(Document::Text(rc_string(format!( "{}val {}: ", @@ -918,22 +999,30 @@ fn class_to_doc( )))), Rc::new(annotation_to_doc(heap, comment_store, &field.annotation)), ) - })) - } - TypeDefinition::Enum { loc: _, variants } => { - parenthesis_surrounded_doc(comma_sep_list(variants, |variant| { + }, + )), + TypeDefinition::Enum { loc: _, variants } => parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + variants, + NO_COMMENT_REFERENCE, + |variant| { if variant.associated_data_types.is_empty() { Document::Text(rc_pstr(heap, variant.name.name)) } else { Document::concat(vec![ Document::Text(rc_pstr(heap, variant.name.name)), - parenthesis_surrounded_doc(comma_sep_list(&variant.associated_data_types, |annot| { - annotation_to_doc(heap, comment_store, annot) - })), + parenthesis_surrounded_doc(comma_sep_list( + heap, + comment_store, + &variant.associated_data_types, + NO_COMMENT_REFERENCE, + |annot| annotation_to_doc(heap, comment_store, annot), + )), ]) } - })) - } + }, + )), }, extends_or_implements_node_to_doc(heap, comment_store, &class.extends_or_implements_nodes), ]; @@ -963,14 +1052,19 @@ fn class_to_doc( pub(super) fn import_to_document( heap: &Heap, + comment_store: &CommentStore, imported_module: ModuleReference, imported_members: &[Id], ) -> Document { let mut documents = vec![]; documents.push(Document::Text(rcs("import "))); - documents.push(braces_surrounded_doc(comma_sep_list(imported_members, |m| { - Document::Text(rc_pstr(heap, m.name)) - }))); + documents.push(braces_surrounded_doc(comma_sep_list( + heap, + comment_store, + imported_members, + NO_COMMENT_REFERENCE, + |m| Document::Text(rc_pstr(heap, m.name)), + ))); documents .push(Document::Text(rc_string(format!(" from {};", imported_module.pretty_print(heap))))); documents.push(Document::LineHard); @@ -1013,7 +1107,12 @@ pub(super) fn source_module_to_document(heap: &Heap, module: &Module<()>) -> Doc ) }) { - documents.push(import_to_document(heap, imported_module, &imported_members)) + documents.push(import_to_document( + heap, + &module.comment_store, + imported_module, + &imported_members, + )) } if !module.imports.is_empty() { documents.push(Document::LineHard); @@ -1059,7 +1158,7 @@ mod tests { let m = parse_source_module_from_text(source, ModuleReference::DUMMY, &mut heap, &mut error_set); for n in &m.imports { - pretty_print_import(&heap, 40, n); + pretty_print_import(&heap, 40, &m.comment_store, n); } for n in &m.toplevels { pretty_print_toplevel(&heap, 40, &m.comment_store, n); @@ -1307,6 +1406,28 @@ Test /* b */ /* c */.VariantName(42)"#, assert_reprint_expr("(a) -> 1", "(a) -> 1"); assert_reprint_expr("(a, b) -> 1", "(a, b) -> 1"); assert_reprint_expr("(a: int) -> 1 + 1", "(a: int) -> 1 + 1"); + assert_reprint_expr("(a: (/* foo */) -> int) -> 1 + 1", "(a: (/* foo */) -> int) -> 1 + 1"); + assert_reprint_expr( + "(a: (int/* foo */) -> int) -> 1 + 1", + "(a: (int, /* foo */) -> int) -> 1 + 1", + ); + assert_reprint_expr( + "(a: (// foo\n) -> int) -> 1 + 1", + r#"( + a: ( + // foo + ) -> int +) -> 1 + 1"#, + ); + assert_reprint_expr( + "(a: (int// foo\n) -> int) -> 1 + 1", + r#"( + a: ( + int, + // foo + ) -> int +) -> 1 + 1"#, + ); assert_reprint_expr("(() -> 1)()", "(() -> 1)()"); assert_reprint_expr("{}", "{ }"); diff --git a/crates/samlang-services/src/ast_differ.rs b/crates/samlang-services/src/ast_differ.rs index 3500f50f..518eafba 100644 --- a/crates/samlang-services/src/ast_differ.rs +++ b/crates/samlang-services/src/ast_differ.rs @@ -259,7 +259,7 @@ impl DiffNode<'_> { DiffNode::Statement(n) => { samlang_printer::pretty_print_statement(heap, 100, comment_store, n) } - DiffNode::Import(n) => samlang_printer::pretty_print_import(heap, 100, n), + DiffNode::Import(n) => samlang_printer::pretty_print_import(heap, 100, comment_store, n), DiffNode::Toplevel(n) => samlang_printer::pretty_print_toplevel(heap, 100, comment_store, n), } } @@ -530,7 +530,7 @@ mod tests { associated_comments: NO_COMMENT_REFERENCE, private: false, name: Id::from(PStr::UPPER_A), - type_parameters: vec![], + type_parameters: None, extends_or_implements_nodes: vec![], type_definition: (), members: vec![], diff --git a/crates/samlang-services/src/gc.rs b/crates/samlang-services/src/gc.rs index 808b8eae..7e1bd50c 100644 --- a/crates/samlang-services/src/gc.rs +++ b/crates/samlang-services/src/gc.rs @@ -1,5 +1,5 @@ use samlang_ast::source::{ - annotation, expr, pattern, Id, Literal, Module, Toplevel, TypeDefinition, TypeParameter, + annotation, expr, pattern, Id, Literal, Module, Toplevel, TypeDefinition, }; use samlang_checker::type_::{FunctionType, NominalType, Type}; use samlang_heap::{Heap, ModuleReference}; @@ -16,11 +16,13 @@ fn mark_annot(heap: &mut Heap, type_: &annotation::T) { fn mark_id_annot(heap: &mut Heap, annot: &annotation::Id) { heap.mark(annot.id.name); - mark_annotations(heap, &annot.type_arguments); + if let Some(targs) = &annot.type_arguments { + mark_annotations(heap, &targs.arguments); + } } fn mark_fn_annot(heap: &mut Heap, annot: &annotation::Function) { - mark_annotations(heap, &annot.argument_types); + mark_annotations(heap, &annot.parameters.parameters); mark_annot(heap, &annot.return_type); } @@ -174,8 +176,8 @@ fn mark_expression(heap: &mut Heap, expr: &expr::E>) { } } -fn mark_type_parameters(heap: &mut Heap, type_parameters: &Vec) { - for tparam in type_parameters { +fn mark_type_parameters(heap: &mut Heap, type_parameters: Option<&annotation::TypeParameters>) { + for tparam in type_parameters.iter().flat_map(|it| &it.parameters) { mark_id(heap, &tparam.name); if let Some(annot) = &tparam.bound { mark_id_annot(heap, annot); @@ -202,7 +204,7 @@ fn mark_module(heap: &mut Heap, module: &Module>) { } for m in toplevel.members_iter() { mark_id(heap, &m.name); - mark_type_parameters(heap, &m.type_parameters); + mark_type_parameters(heap, m.type_parameters.as_ref()); mark_fn_annot(heap, &m.type_); } if let Toplevel::Class(c) = toplevel { diff --git a/crates/samlang-services/src/global_searcher.rs b/crates/samlang-services/src/global_searcher.rs index 7f96502d..957358fe 100644 --- a/crates/samlang-services/src/global_searcher.rs +++ b/crates/samlang-services/src/global_searcher.rs @@ -21,7 +21,7 @@ fn search_annot( annotation::T::Primitive(_, _, _) | annotation::T::Generic(_, _) => {} annotation::T::Id(annot) => search_id_annot(annot, request, collector), annotation::T::Fn(annot) => { - for a in &annot.argument_types { + for a in &annot.parameters.parameters { search_annot(a, request, collector); } search_annot(&annot.return_type, request, collector); @@ -42,7 +42,7 @@ fn search_id_annot( } _ => {} } - for annot in &annotation.type_arguments { + for annot in annotation.type_arguments.iter().flat_map(|it| &it.arguments) { search_annot(annot, request, collector); } } diff --git a/crates/samlang-services/src/variable_definition.rs b/crates/samlang-services/src/variable_definition.rs index 037d5d87..d8d0fcab 100644 --- a/crates/samlang-services/src/variable_definition.rs +++ b/crates/samlang-services/src/variable_definition.rs @@ -507,7 +507,7 @@ class Main { fn rename_test_1() { let source = r#" class Main { - function test(a: int, b: bool): unit = { + function test(a: int, b: (int, bool, Str, int, bool, Str, int, bool, Str, /* foo */) -> bool, c: (/* foo */) -> bool, d: () -> bool): unit = { let c = a.foo; } } @@ -523,7 +523,23 @@ interface Foo {} ); assert_eq!( r#"class Main { - function test(renAmeD: int, b: bool): unit = { + function test( + renAmeD: int, + b: ( + int, + bool, + Str, + int, + bool, + Str, + int, + bool, + Str, + /* foo */ + ) -> bool, + c: (/* foo */) -> bool, + d: () -> bool + ): unit = { let c = renAmeD.foo; } }