// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use qsc_ast::ast::{self, Idents, TypeParameter as AstTypeParameter}; use qsc_frontend::resolve; use qsc_hir::{ hir, ty::{self, TypeParameter as HirTypeParameter}, }; use regex_lite::Regex; use std::{ fmt::{Display, Formatter, Result}, rc::Rc, }; /// Trait describing a struct capable of resolving various ids found in the AST and HIR. pub trait Lookup { /// Looks up the type of a node in user code fn get_ty(&self, expr_id: ast::NodeId) -> Option<&ty::Ty>; /// Looks up the resolution of a node in user code fn get_res(&self, id: ast::NodeId) -> Option<&resolve::Res>; /// Returns the hir `Item` node referred to by `item_id`, /// along with the `Package` and `PackageId` for the package /// that it was found in. fn resolve_item_relative_to_user_package( &self, item_id: &hir::ItemId, ) -> (&hir::Item, &hir::Package, hir::ItemId); /// Returns the hir `Item` node referred to by `res`. /// `Res`s can resolve to external packages, and the references /// are relative, so here we also need the /// local `PackageId` that the `res` itself came from. fn resolve_item_res(&self, res: &hir::Res) -> (&hir::Item, hir::ItemId); /// Returns the hir `Item` node referred to by `item_id`. /// `ItemId`s can refer to external packages, and the references /// are relative, so here we also need the local `PackageId` /// that the `ItemId` originates from. fn resolve_item(&self, item_id: &hir::ItemId) -> (&hir::Item, &hir::Package, hir::ItemId); } pub struct CodeDisplay<'a> { pub compilation: &'a dyn Lookup, } #[allow(clippy::unused_self)] impl<'a> CodeDisplay<'a> { #[must_use] pub fn hir_callable_decl(&self, decl: &'a hir::CallableDecl) -> impl Display + '_ { HirCallableDecl { decl } } #[must_use] pub fn ast_callable_decl(&self, decl: &'a ast::CallableDecl) -> impl Display + '_ { AstCallableDecl { lookup: self.compilation, decl, } } #[must_use] pub fn name_ty_id(&self, name: &'a str, ty_id: ast::NodeId) -> impl Display + '_ { NameTyId { lookup: self.compilation, name, ty_id, } } #[must_use] pub fn ident_ty(&self, ident: &'a ast::Ident, ty: &'a ast::Ty) -> impl Display + '_ { IdentTy { ident, ty } } #[must_use] pub fn ident_ty_def(&self, ident: &'a ast::Ident, def: &'a ast::TyDef) -> impl Display + 'a { IdentTyDef { ident, def } } #[must_use] pub fn struct_decl(&self, decl: &'a ast::StructDecl) -> impl Display + 'a { StructDecl { decl } } #[must_use] pub fn hir_udt_field(&self, field: &'a ty::UdtField) -> impl Display + '_ { HirUdtField { field } } #[must_use] pub fn hir_udt(&self, udt: &'a ty::Udt) -> impl Display + '_ { HirUdt::new(udt) } #[must_use] pub fn hir_pat(&self, pat: &'a hir::Pat) -> impl Display + '_ { HirPat { pat } } #[must_use] pub fn get_param_offset(&self, decl: &hir::CallableDecl) -> u32 { HirCallableDecl { decl }.get_param_offset() } // The rest of the display implementations are not made public b/c they're not used, // but there's no reason they couldn't be } // Display impls for each syntax/hir element we may encounter struct IdentTy<'a> { ident: &'a ast::Ident, ty: &'a ast::Ty, } impl Display for IdentTy<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{} : {}", self.ident.name, AstTy { ty: self.ty }) } } struct NameTyId<'a> { lookup: &'a dyn Lookup, name: &'a str, ty_id: ast::NodeId, } impl Display for NameTyId<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!( f, "{} : {}", self.name, TyId { lookup: self.lookup, ty_id: self.ty_id, }, ) } } struct HirCallableDecl<'a> { decl: &'a hir::CallableDecl, } impl HirCallableDecl<'_> { fn get_param_offset(&self) -> u32 { let offset = match self.decl.kind { hir::CallableKind::Function => "function".len(), hir::CallableKind::Operation => "operation".len(), } + 1 // this is for the space between keyword and name + self.decl.name.name.len() + display_type_params(&self.decl.generics).len(); u32::try_from(offset) .expect("failed to cast usize to u32 while calculating parameter offset") } } impl Display for HirCallableDecl<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { let kind = match self.decl.kind { hir::CallableKind::Function => "function", hir::CallableKind::Operation => "operation", }; write!(f, "{} {}", kind, self.decl.name.name)?; let type_params = display_type_params(&self.decl.generics); write!(f, "{type_params}")?; let input = HirPat { pat: &self.decl.input, }; if matches!(self.decl.input.kind, hir::PatKind::Tuple(_)) { write!(f, "{input}")?; } else { write!(f, "({input})")?; } write!( f, " : {}{}", self.decl.output.display(), FunctorSetValue { functors: self.decl.functors, }, ) } } struct AstCallableDecl<'a> { lookup: &'a dyn Lookup, decl: &'a ast::CallableDecl, } impl Display for AstCallableDecl<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { let kind = match self.decl.kind { ast::CallableKind::Function => "function", ast::CallableKind::Operation => "operation", }; let functors = ast_callable_functors(self.decl); let functors = FunctorSetValue { functors }; write!(f, "{} {}", kind, self.decl.name.name)?; if !self.decl.generics.is_empty() { let type_params = self .decl .generics .iter() .map( |AstTypeParameter { ty, constraints, .. }| { format!( "{}{}", ty.name, if constraints.0.is_empty() { Default::default() } else { format!( ": {}", constraints .0 .iter() .map(|bound| { let constraint_parameters = bound .parameters .iter() .map(|x| format!("{}", AstTy { ty: &x.ty })) .collect::>() .join(", "); format!( "{}{}", bound.name.name, if constraint_parameters.is_empty() { Default::default() } else { format!("[{constraint_parameters}]") } ) }) .collect::>() .join(" + ") ) } ) }, ) .collect::>() .join(", "); write!(f, "<{type_params}>")?; } let input = AstPat { pat: &self.decl.input, lookup: self.lookup, }; if matches!(*self.decl.input.kind, ast::PatKind::Tuple(_)) { write!(f, "{input}")?; } else { write!(f, "({input})")?; } write!( f, " : {}{}", AstTy { ty: &self.decl.output }, functors, ) } } struct HirPat<'a> { pat: &'a hir::Pat, } impl Display for HirPat<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match &self.pat.kind { hir::PatKind::Bind(name) => write!(f, "{} : {}", name.name, self.pat.ty.display()), hir::PatKind::Discard => write!(f, "_ : {}", self.pat.ty.display()), hir::PatKind::Tuple(items) => { let mut elements = items.iter(); if let Some(elem) = elements.next() { write!(f, "({}", HirPat { pat: elem })?; for elem in elements { write!(f, ", {}", HirPat { pat: elem })?; } write!(f, ")") } else { write!(f, "()") } } hir::PatKind::Err => write!(f, "?"), } } } struct AstPat<'a> { lookup: &'a dyn Lookup, pat: &'a ast::Pat, } impl Display for AstPat<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match &*self.pat.kind { ast::PatKind::Bind(ident, anno) => match anno { Some(ty) => write!(f, "{}", IdentTy { ident, ty }), None => write!( f, "{}", NameTyId { lookup: self.lookup, name: &ident.name, ty_id: self.pat.id } ), }, ast::PatKind::Discard(anno) => match anno { Some(ty) => write!(f, "{}", AstTy { ty }), None => write!( f, "_ : {}", TyId { lookup: self.lookup, ty_id: self.pat.id, } ), }, ast::PatKind::Elided => write!(f, "..."), ast::PatKind::Paren(item) => write!( f, "{}", AstPat { lookup: self.lookup, pat: item, } ), ast::PatKind::Tuple(items) => { let mut elements = items.iter(); if let Some(elem) = elements.next() { write!( f, "({}", AstPat { lookup: self.lookup, pat: elem, } )?; for elem in elements { write!( f, ", {}", AstPat { lookup: self.lookup, pat: elem, } )?; } write!(f, ")") } else { write!(f, "()") } } ast::PatKind::Err => write!(f, "?"), } } } struct IdentTyDef<'a> { ident: &'a ast::Ident, def: &'a ast::TyDef, } impl Display for IdentTyDef<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if let Some(fields) = as_struct(self.def) { write!(f, "struct {} ", self.ident.name)?; fmt_brace_seq(f, &fields, |item| IdentTy { ident: &item.name, ty: &item.ty, }) } else { write!( f, "newtype {} = {}", self.ident.name, TyDef { def: self.def } ) } } } struct StructDecl<'a> { decl: &'a ast::StructDecl, } impl Display for StructDecl<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "struct {} ", self.decl.name.name)?; fmt_brace_seq(f, &self.decl.fields, |item| IdentTy { ident: &item.name, ty: &item.ty, }) } } struct HirUdt<'a> { udt: &'a ty::Udt, is_struct: bool, } impl<'a> HirUdt<'a> { fn new(udt: &'a ty::Udt) -> Self { HirUdt { udt, is_struct: udt.is_struct(), } } } impl Display for HirUdt<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if self.is_struct { match &self.udt.definition.kind { ty::UdtDefKind::Tuple(fields) => { write!(f, "struct {} ", self.udt.name)?; fmt_brace_seq(f, fields, UdtDef::new)?; } ty::UdtDefKind::Field(_) => {} } Ok(()) } else { let udt_def = UdtDef::new(&self.udt.definition); write!(f, "newtype {} = {}", self.udt.name, udt_def) } } } struct UdtDef<'a> { name: Option>, kind: UdtDefKind<'a>, } enum UdtDefKind<'a> { SingleTy(&'a ty::Ty), TupleTy(Vec>), } impl<'a> UdtDef<'a> { pub fn new(def: &'a ty::UdtDef) -> Self { match &def.kind { ty::UdtDefKind::Field(field) => UdtDef { name: field.name.clone(), kind: UdtDefKind::SingleTy(&field.ty), }, ty::UdtDefKind::Tuple(defs) => UdtDef { name: None, kind: UdtDefKind::TupleTy(defs.iter().map(UdtDef::new).collect()), }, } } } impl Display for UdtDef<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if let Some(name) = &self.name { write!(f, "{name} : ")?; } match &self.kind { UdtDefKind::SingleTy(ty) => { write!(f, "{}", ty.display()) } UdtDefKind::TupleTy(defs) => fmt_tuple(f, defs, |def| def), } } } struct HirUdtField<'a> { field: &'a ty::UdtField, } impl Display for HirUdtField<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if let Some(doc) = &self.field.doc { for line in doc.lines() { writeln!(f, "/// {line}")?; } } if let Some(name) = &self.field.name { write!(f, "{name} : ")?; } write!(f, "{}", self.field.ty.display()) } } struct FunctorSetValue { functors: ty::FunctorSetValue, } impl Display for FunctorSetValue { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if let ty::FunctorSetValue::Empty = self.functors { Ok(()) } else { write!(f, " is {}", self.functors) } } } struct TyId<'a> { lookup: &'a dyn Lookup, ty_id: ast::NodeId, } impl Display for TyId<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if let Some(ty) = self.lookup.get_ty(self.ty_id) { write!(f, "{}", ty.display()) } else { write!(f, "?") } } } struct AstTy<'a> { ty: &'a ast::Ty, } impl Display for AstTy<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self.ty.kind.as_ref() { ast::TyKind::Array(ty) => write!(f, "{}[]", AstTy { ty }), ast::TyKind::Arrow(kind, input, output, functors) => { let arrow = match kind { ast::CallableKind::Function => "->", ast::CallableKind::Operation => "=>", }; write!( f, "({} {} {}{})", AstTy { ty: input }, arrow, AstTy { ty: output }, FunctorExpr { functors } ) } ast::TyKind::Hole => write!(f, "_"), ast::TyKind::Paren(ty) => write!(f, "{}", AstTy { ty }), ast::TyKind::Path(path) => write!(f, "{}", AstPathKind { path }), ast::TyKind::Param(AstTypeParameter { ty, .. }) => write!(f, "{}", ty.name), ast::TyKind::Tuple(tys) => fmt_tuple(f, tys, |ty| AstTy { ty }), ast::TyKind::Err => write!(f, "?"), } } } struct FunctorExpr<'a> { functors: &'a Option>, } impl Display for FunctorExpr<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self.functors { Some(functors) => { let functors = eval_functor_expr(functors); write!(f, "{}", FunctorSetValue { functors }) } None => Ok(()), } } } struct AstPathKind<'a> { path: &'a ast::PathKind, } impl Display for AstPathKind<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if let ast::PathKind::Ok(path) = self.path { write!(f, "{}", path.full_name()) } else { write!(f, "?") } } } struct TyDef<'a> { def: &'a ast::TyDef, } impl Display for TyDef<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self.def.kind.as_ref() { ast::TyDefKind::Field(name, ty, _) => match name { Some(name) => write!(f, "{} : {}", name.name, AstTy { ty }), None => write!(f, "{}", AstTy { ty }), }, ast::TyDefKind::Paren(def) => write!(f, "{}", TyDef { def }), ast::TyDefKind::Tuple(tys) => fmt_tuple(f, tys, |def| TyDef { def }), ast::TyDefKind::Err => write!(f, "?"), } } } fn fmt_tuple<'a, I, O>( formatter: &mut Formatter, items: &'a [I], map: impl Fn(&'a I) -> O, ) -> Result where O: Display, { let mut elements = items.iter(); if let Some(elem) = elements.next() { write!(formatter, "({}", map(elem))?; if elements.len() == 0 { write!(formatter, ",)")?; } else { for elem in elements { write!(formatter, ", {}", map(elem))?; } write!(formatter, ")")?; } } else { write!(formatter, "Unit")?; } Ok(()) } fn fmt_brace_seq<'a, I, O>( formatter: &mut Formatter<'_>, items: &'a [I], map: impl Fn(&'a I) -> O, ) -> Result where O: Display, { write!(formatter, "{{ ")?; if let Some((last, most)) = items.split_last() { for item in most { write!(formatter, "{}, ", map(item))?; } write!(formatter, "{} ", map(last))?; } write!(formatter, "}}") } fn display_type_params(generics: &[HirTypeParameter]) -> String { let type_params = generics .iter() .filter_map(|generic| match generic { HirTypeParameter::Ty { name, bounds } => Some(format!( "{}{}", name, if bounds.is_empty() { Default::default() } else { format!(": {bounds}") } )), HirTypeParameter::Functor(_) => None, }) .collect::>() .join(", "); if type_params.is_empty() { type_params } else { format!("<{type_params}>") } } // // helpers that don't manipulate any strings // fn ast_callable_functors(callable: &ast::CallableDecl) -> ty::FunctorSetValue { let mut functors = callable .functors .as_ref() .map_or(ty::FunctorSetValue::Empty, |f| { eval_functor_expr(f.as_ref()) }); if let ast::CallableBody::Specs(specs) = callable.body.as_ref() { for spec in specs { let spec_functors = match spec.spec { ast::Spec::Body => ty::FunctorSetValue::Empty, ast::Spec::Adj => ty::FunctorSetValue::Adj, ast::Spec::Ctl => ty::FunctorSetValue::Ctl, ast::Spec::CtlAdj => ty::FunctorSetValue::CtlAdj, }; functors = functors.union(&spec_functors); } } functors } fn eval_functor_expr(expr: &ast::FunctorExpr) -> ty::FunctorSetValue { match expr.kind.as_ref() { ast::FunctorExprKind::BinOp(op, lhs, rhs) => { let lhs_functors = eval_functor_expr(lhs); let rhs_functors = eval_functor_expr(rhs); match op { ast::SetOp::Union => lhs_functors.union(&rhs_functors), ast::SetOp::Intersect => lhs_functors.intersect(&rhs_functors), } } ast::FunctorExprKind::Lit(ast::Functor::Adj) => ty::FunctorSetValue::Adj, ast::FunctorExprKind::Lit(ast::Functor::Ctl) => ty::FunctorSetValue::Ctl, ast::FunctorExprKind::Paren(inner) => eval_functor_expr(inner), } } fn as_struct(ty_def: &ast::TyDef) -> Option> { match ty_def.kind.as_ref() { ast::TyDefKind::Paren(inner) => as_struct(inner), ast::TyDefKind::Tuple(fields) => { let mut converted_fields = Vec::new(); for field in fields { let field = remove_parens(field); match field.kind.as_ref() { ast::TyDefKind::Field(Some(name), field_ty, doc) => { converted_fields.push(ast::FieldDef { id: field.id, span: field.span, name: name.clone(), doc: doc.as_ref().map(Rc::clone), ty: field_ty.clone(), }); } _ => return None, } } Some(converted_fields) } ast::TyDefKind::Err | ast::TyDefKind::Field(..) => None, } } fn remove_parens(ty_def: &ast::TyDef) -> &ast::TyDef { match ty_def.kind.as_ref() { ast::TyDefKind::Paren(inner) => remove_parens(inner.as_ref()), _ => ty_def, } } // // parsing functions for working with doc comments // /// Takes a doc string from Q# and increases all of the markdown header levels by one level. /// i.e. `# Summary` becomes `## Summary` #[must_use] pub fn increase_header_level(doc: &str) -> String { let re = Regex::new(r"(?mi)^(#+)( [\s\S]+?)$").expect("Invalid regex"); re.replace_all(doc, "$1#$2").to_string() } /// Takes a doc string from Q# and returns the contents of the `# Summary` section. If no /// such section can be found, returns the original doc string. #[must_use] pub fn parse_doc_for_summary(doc: &str) -> String { let re = Regex::new(r"(?mi)(?:^# Summary$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex"); match re.captures(doc) { Some(captures) => { let capture = captures .get(1) .expect("Didn't find the capture for the given regex"); capture.as_str() } None => doc, } .trim() .to_string() } /// Takes a doc string from a Q# callable and the name of a parameter of /// that callable. Returns the description of that parameter found in the /// doc string. If no description is found, returns the empty string. #[must_use] pub fn parse_doc_for_param(doc: &str, param: &str) -> String { let re = Regex::new(r"(?mi)(?:^# Input$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex"); let input = match re.captures(doc) { Some(captures) => { let capture = captures .get(1) .expect("Didn't find the capture for the given regex"); capture.as_str() } None => return String::new(), } .trim(); let re = Regex::new(format!(r"(?mi)(?:^## {param}$)([\s\S]*?)(?:(^(#|##) .*)|\z)").as_str()) .expect("Invalid regex"); match re.captures(input) { Some(captures) => { let capture = captures .get(1) .expect("Didn't find the capture for the given regex"); capture.as_str() } None => return String::new(), } .trim() .to_string() }