Fork of daniellemaywood.uk/gleam — Wasm codegen work
20 kB
584 lines
1use std::{
2 collections::{HashMap, HashSet},
3 ops::Deref,
4};
5
6use ecow::{EcoString, eco_format};
7use itertools::Itertools;
8
9use crate::{
10 ast::{
11 ArgNames, CustomType, Definition, Function, ModuleConstant, Publicity,
12 RecordConstructorArg, SrcSpan, TypeAlias, TypedArg, TypedDefinition,
13 TypedRecordConstructor,
14 },
15 docvec,
16 pretty::{Document, Documentable, break_, join, line, nil, zero_width_string},
17 type_::{
18 Deprecation, PRELUDE_MODULE_NAME, PRELUDE_PACKAGE_NAME, Type, TypeVar, printer::Names,
19 },
20};
21
22use super::{
23 DocsValues, TypeConstructor, TypeConstructorArg, TypeDefinition, markdown_documentation,
24 source_links::SourceLinker, text_documentation,
25};
26
27#[derive(Clone, Copy)]
28pub struct PrintOptions {
29 pub print_highlighting: bool,
30 pub print_links: bool,
31}
32
33impl PrintOptions {
34 pub fn all() -> Self {
35 Self {
36 print_highlighting: true,
37 print_links: true,
38 }
39 }
40}
41
42pub struct Printer<'a> {
43 options: PrintOptions,
44 names: &'a Names,
45
46 package: EcoString,
47 module: EcoString,
48
49 /// Type variables which don't have annotated names and we have generated
50 /// names for.
51 printed_type_variables: HashMap<u64, EcoString>,
52 /// Names of type variables that we have generated or printed in a given
53 /// definition. This ensures that we have no duplicate generated names.
54 printed_type_variable_names: HashSet<EcoString>,
55 /// An incrementing number used to generate the next type variable name.
56 /// `0` becomes `a`, `1` becomes `b`, etc.
57 next_type_variable_id: u64,
58}
59
60impl Printer<'_> {
61 pub fn new(package: EcoString, module: EcoString, names: &Names) -> Printer<'_> {
62 Printer {
63 options: PrintOptions::all(),
64 names,
65 package,
66 module,
67 printed_type_variables: HashMap::new(),
68 printed_type_variable_names: HashSet::new(),
69 next_type_variable_id: 0,
70 }
71 }
72
73 // This is currently only used in the tests, though it might be useful in
74 // application code in future. If it is needed, simply remove this attribute.
75 #[cfg(test)]
76 pub fn set_options(&mut self, options: PrintOptions) {
77 self.options = options;
78 }
79
80 pub fn type_definition<'a>(
81 &mut self,
82 source_links: &SourceLinker,
83 statement: &'a TypedDefinition,
84 ) -> Option<TypeDefinition<'a>> {
85 match statement {
86 Definition::CustomType(CustomType {
87 publicity: Publicity::Public,
88 location,
89 name,
90 constructors,
91 documentation,
92 deprecation,
93 opaque,
94 parameters,
95 ..
96 }) => Some(TypeDefinition {
97 name,
98 definition: print(self.custom_type(name, parameters, constructors, *opaque)),
99 documentation: markdown_documentation(documentation),
100 text_documentation: text_documentation(documentation),
101 deprecation_message: match deprecation {
102 Deprecation::NotDeprecated => "".to_string(),
103 Deprecation::Deprecated { message } => message.to_string(),
104 },
105 constructors: if *opaque {
106 Vec::new()
107 } else {
108 constructors
109 .iter()
110 .map(|constructor| TypeConstructor {
111 definition: print(self.record_constructor(constructor)),
112 documentation: markdown_documentation(&constructor.documentation),
113 text_documentation: text_documentation(&constructor.documentation),
114 arguments: constructor
115 .arguments
116 .iter()
117 .filter_map(|arg| arg.label.as_ref().map(|(_, label)| (arg, label)))
118 .map(|(argument, label)| TypeConstructorArg {
119 name: label.trim_end().to_string(),
120 doc: markdown_documentation(&argument.doc),
121 })
122 .filter(|arg| !arg.doc.is_empty())
123 .collect(),
124 })
125 .collect()
126 },
127 source_url: source_links.url(*location),
128 opaque: *opaque,
129 }),
130 Definition::TypeAlias(TypeAlias {
131 publicity: Publicity::Public,
132 location,
133 alias: name,
134 parameters,
135 type_,
136 documentation,
137 deprecation,
138 ..
139 }) => Some(TypeDefinition {
140 name,
141 definition: print(self.type_alias(name, type_, parameters).group()),
142 documentation: markdown_documentation(documentation),
143 text_documentation: text_documentation(documentation),
144 constructors: vec![],
145 source_url: source_links.url(*location),
146 deprecation_message: match deprecation {
147 Deprecation::NotDeprecated => "".to_string(),
148 Deprecation::Deprecated { message } => message.to_string(),
149 },
150 opaque: false,
151 }),
152 Definition::TypeAlias(_)
153 | Definition::CustomType(_)
154 | Definition::Function(_)
155 | Definition::Import(_)
156 | Definition::ModuleConstant(_) => None,
157 }
158 }
159
160 pub fn value<'a>(
161 &mut self,
162 source_links: &SourceLinker,
163 statement: &'a TypedDefinition,
164 ) -> Option<DocsValues<'a>> {
165 // Ensure that any type variables we printed in previous definitions don't
166 // affect our printing of this definition. Two type variables in different
167 // definitions can have the same name without clashing.
168 self.printed_type_variable_names.clear();
169 self.next_type_variable_id = 0;
170
171 match statement {
172 Definition::Function(Function {
173 publicity: Publicity::Public,
174 name: Some((_, name)),
175 documentation: doc,
176 location,
177 deprecation,
178 arguments,
179 return_type,
180 ..
181 }) => Some(DocsValues {
182 name,
183 definition: print(self.function_signature(name, arguments, return_type)),
184 documentation: markdown_documentation(doc),
185 text_documentation: text_documentation(doc),
186 source_url: source_links.url(*location),
187 deprecation_message: match deprecation {
188 Deprecation::NotDeprecated => "".to_string(),
189 Deprecation::Deprecated { message } => message.to_string(),
190 },
191 }),
192
193 Definition::ModuleConstant(ModuleConstant {
194 publicity: Publicity::Public,
195 documentation,
196 location,
197 name,
198 type_,
199 deprecation,
200 ..
201 }) => Some(DocsValues {
202 name,
203 definition: print(self.constant(name, type_)),
204 documentation: markdown_documentation(documentation),
205 text_documentation: text_documentation(documentation),
206 source_url: source_links.url(*location),
207 deprecation_message: match deprecation {
208 Deprecation::NotDeprecated => "".to_string(),
209 Deprecation::Deprecated { message } => message.to_string(),
210 },
211 }),
212
213 Definition::TypeAlias(_)
214 | Definition::CustomType(_)
215 | Definition::Function(_)
216 | Definition::Import(_)
217 | Definition::ModuleConstant(_) => None,
218 }
219 }
220
221 fn custom_type<'a>(
222 &mut self,
223 name: &'a str,
224 parameters: &'a [(SrcSpan, EcoString)],
225 constructors: &'a [TypedRecordConstructor],
226 opaque: bool,
227 ) -> Document<'a> {
228 let arguments = if parameters.is_empty() {
229 nil()
230 } else {
231 Self::wrap_arguments(
232 parameters
233 .iter()
234 .map(|(_, parameter)| self.variable(parameter)),
235 )
236 };
237
238 let keywords = if opaque {
239 "pub opaque type "
240 } else {
241 "pub type "
242 };
243
244 let type_head = docvec![self.keyword(keywords), self.title(name), arguments];
245
246 if constructors.is_empty() || opaque {
247 return type_head;
248 }
249
250 let constructors = constructors
251 .iter()
252 .map(|constructor| {
253 line()
254 .append(self.record_constructor(constructor))
255 .nest(INDENT)
256 })
257 .collect_vec();
258
259 docvec![type_head, " {", constructors, line(), "}"]
260 }
261
262 pub fn record_constructor<'a>(
263 &mut self,
264 constructor: &'a TypedRecordConstructor,
265 ) -> Document<'a> {
266 if constructor.arguments.is_empty() {
267 return self.title(&constructor.name);
268 }
269
270 let arguments = constructor.arguments.iter().map(
271 |RecordConstructorArg { label, type_, .. }| match label {
272 Some((_, label)) => self.variable(label).append(": ").append(self.type_(type_)),
273 None => self.type_(type_),
274 },
275 );
276
277 let arguments = Self::wrap_arguments(arguments);
278
279 docvec![self.title(&constructor.name), arguments]
280 }
281
282 fn type_alias<'a>(
283 &mut self,
284 name: &'a str,
285 type_: &Type,
286 parameters: &[(SrcSpan, EcoString)],
287 ) -> Document<'a> {
288 let parameters = if parameters.is_empty() {
289 nil()
290 } else {
291 let arguments = parameters
292 .iter()
293 .map(|(_, parameter)| self.variable(parameter));
294 Self::wrap_arguments(arguments)
295 };
296
297 docvec![
298 self.keyword("pub type "),
299 self.title(name),
300 parameters,
301 " =",
302 line().append(self.type_(type_)).nest(INDENT)
303 ]
304 }
305
306 fn constant<'a>(&mut self, name: &'a str, type_: &Type) -> Document<'a> {
307 self.register_local_type_variable_names(type_);
308
309 docvec![
310 self.keyword("pub const "),
311 self.title(name),
312 ": ",
313 self.type_(type_)
314 ]
315 }
316
317 fn function_signature<'a>(
318 &mut self,
319 name: &'a str,
320 arguments: &'a [TypedArg],
321 return_type: &Type,
322 ) -> Document<'a> {
323 for argument in arguments {
324 self.register_local_type_variable_names(&argument.type_);
325 }
326 self.register_local_type_variable_names(return_type);
327
328 let arguments = arguments.iter().map(|argument| {
329 let name = self.variable(self.argument_name(argument));
330 docvec![name, ": ", self.type_(&argument.type_)].group()
331 });
332 let arguments = Self::wrap_arguments(arguments);
333
334 docvec![
335 self.keyword("pub fn "),
336 self.title(name),
337 arguments,
338 " -> ",
339 self.type_(return_type)
340 ]
341 .group()
342 }
343
344 fn argument_name<'a>(&self, arg: &'a TypedArg) -> Document<'a> {
345 match &arg.names {
346 ArgNames::Named { name, .. } => name.to_doc(),
347 ArgNames::NamedLabelled { label, name, .. } => docvec![label, " ", name],
348 // We remove the underscore from discarded function arguments since we don't want to
349 // expose this kind of detail: https://github.com/gleam-lang/gleam/issues/2561
350 ArgNames::Discard { name, .. } => match name.strip_prefix('_').unwrap_or(name) {
351 "" => "arg".to_doc(),
352 name => name.to_doc(),
353 },
354 ArgNames::LabelledDiscard { label, name, .. } => {
355 docvec![label, " ", name.strip_prefix('_').unwrap_or(name).to_doc()]
356 }
357 }
358 }
359
360 fn wrap_arguments<'a>(arguments: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
361 break_("(", "(")
362 .append(join(arguments, break_(",", ", ")))
363 .nest_if_broken(INDENT)
364 .append(break_(",", ""))
365 .append(")")
366 }
367
368 fn type_arguments<'a>(arguments: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
369 break_("", "")
370 .append(join(arguments, break_(",", ", ")))
371 .nest_if_broken(INDENT)
372 .append(break_(",", ""))
373 .group()
374 .surround("(", ")")
375 }
376
377 fn type_(&mut self, type_: &Type) -> Document<'static> {
378 match type_ {
379 Type::Named {
380 package,
381 module,
382 name,
383 args,
384 ..
385 } => {
386 let name = self.named_type_name(package, module, name);
387 if args.is_empty() {
388 name
389 } else {
390 name.append(Self::type_arguments(
391 args.iter().map(|argument| self.type_(argument)),
392 ))
393 }
394 }
395 Type::Fn { args, return_ } => docvec![
396 self.keyword("fn"),
397 Self::type_arguments(args.iter().map(|argument| self.type_(argument))),
398 " -> ",
399 self.type_(return_)
400 ],
401 Type::Tuple { elements } => docvec![
402 "#",
403 Self::type_arguments(elements.iter().map(|element| self.type_(element))),
404 ],
405 Type::Var { type_ } => match type_.as_ref().borrow().deref() {
406 TypeVar::Link { type_ } => self.type_(type_),
407
408 TypeVar::Unbound { id } | TypeVar::Generic { id } => {
409 let name = self.type_variable(*id);
410 self.variable(name)
411 }
412 },
413 }
414 }
415
416 fn type_variable(&mut self, id: u64) -> EcoString {
417 if let Some(name) = self.names.get_type_variable(id) {
418 return name.clone();
419 }
420
421 if let Some(name) = self.printed_type_variables.get(&id) {
422 return name.clone();
423 }
424
425 loop {
426 let name = self.next_letter();
427 if !self.printed_type_variable_names.contains(&name) {
428 _ = self.printed_type_variable_names.insert(name.clone());
429 _ = self.printed_type_variables.insert(id, name.clone());
430 return name;
431 }
432 }
433 }
434
435 // Copied from the `next_letter` method of the `type_::printer`.
436 fn next_letter(&mut self) -> EcoString {
437 let alphabet_length = 26;
438 let char_offset = b'a';
439 let mut chars = vec![];
440 let mut n;
441 let mut rest = self.next_type_variable_id;
442
443 loop {
444 n = rest % alphabet_length;
445 rest = rest / alphabet_length;
446 chars.push((n as u8 + char_offset) as char);
447
448 if rest == 0 {
449 break;
450 }
451 rest -= 1
452 }
453
454 self.next_type_variable_id += 1;
455 chars.into_iter().rev().collect()
456 }
457
458 fn named_type_name(&self, package: &str, module: &str, name: &EcoString) -> Document<'static> {
459 if package == PRELUDE_PACKAGE_NAME && module == PRELUDE_MODULE_NAME {
460 self.title(name)
461 } else if package == self.package && module == self.module {
462 self.link(eco_format!("#{name}"), self.title(name), None)
463 } else {
464 let module_name = module.split('/').next_back().unwrap_or(module);
465 let qualified_name = docvec![
466 self.variable(EcoString::from(module_name)),
467 ".",
468 self.title(name)
469 ];
470 let title = eco_format!("{module}.{{type {name}}}");
471
472 self.link(
473 eco_format!("https://hexdocs.pm/{package}/{module}.html#{name}"),
474 qualified_name,
475 Some(title),
476 )
477 }
478 }
479
480 /// Walk a type and register all the type variable names which occur within
481 /// it. This is to ensure that when generating type variable names for
482 /// unannotated arguments, we don't print any that clash with existing names.
483 ///
484 /// We preregister all names before actually printing anything, because we
485 /// could run into code like this:
486 ///
487 /// ```gleam
488 /// pub fn wibble(_, _: a) -> b {}
489 /// ```
490 ///
491 /// If we did not preregister the type variables in this case, we would end
492 /// up printing `fn wibble(_: a, _: a) -> b` which is not correct.
493 ///
494 fn register_local_type_variable_names(&mut self, type_: &Type) {
495 match type_ {
496 Type::Named { args, .. } => {
497 for arg in args {
498 self.register_local_type_variable_names(arg);
499 }
500 }
501 Type::Fn { args, return_ } => {
502 for arg in args {
503 self.register_local_type_variable_names(arg);
504 }
505 self.register_local_type_variable_names(return_);
506 }
507 Type::Var { type_ } => match type_.borrow().deref() {
508 TypeVar::Link { type_ } => self.register_local_type_variable_names(type_),
509 TypeVar::Unbound { id } | TypeVar::Generic { id } => {
510 if let Some(name) = self.names.get_type_variable(*id) {
511 _ = self.printed_type_variable_names.insert(name.clone());
512 }
513 }
514 },
515 Type::Tuple { elements } => {
516 for element in elements {
517 self.register_local_type_variable_names(element);
518 }
519 }
520 }
521 }
522
523 fn keyword<'a>(&self, keyword: impl Documentable<'a>) -> Document<'a> {
524 if !self.options.print_highlighting {
525 return keyword.to_doc();
526 }
527
528 keyword.to_doc().surround(
529 zero_width_string(r#"<span class="hljs-keyword">"#.into()),
530 zero_width_string("</span>".into()),
531 )
532 }
533
534 fn title<'a>(&self, name: impl Documentable<'a>) -> Document<'a> {
535 if !self.options.print_highlighting {
536 return name.to_doc();
537 }
538
539 name.to_doc().surround(
540 zero_width_string(r#"<span class="hljs-title">"#.into()),
541 zero_width_string("</span>".into()),
542 )
543 }
544
545 fn variable<'a>(&self, name: impl Documentable<'a>) -> Document<'a> {
546 if !self.options.print_highlighting {
547 return name.to_doc();
548 }
549
550 name.to_doc().surround(
551 zero_width_string(r#"<span class="hljs-variable">"#.into()),
552 zero_width_string("</span>".into()),
553 )
554 }
555
556 fn link<'a>(
557 &self,
558 href: EcoString,
559 name: impl Documentable<'a>,
560 title: Option<EcoString>,
561 ) -> Document<'a> {
562 if !self.options.print_links {
563 return name.to_doc();
564 }
565
566 let opening_tag = if let Some(title) = title {
567 eco_format!(r#"<a href="{href}" title="{title}">"#)
568 } else {
569 eco_format!(r#"<a href="{href}">"#)
570 };
571
572 name.to_doc().surround(
573 zero_width_string(opening_tag),
574 zero_width_string("</a>".into()),
575 )
576 }
577}
578
579const MAX_COLUMNS: isize = 65;
580const INDENT: isize = 2;
581
582fn print(doc: Document<'_>) -> String {
583 doc.to_pretty_string(MAX_COLUMNS)
584}