Fork of daniellemaywood.uk/gleam — Wasm codegen work
30 kB
945 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2021 The Gleam contributors
3
4#![allow(warnings)]
5
6mod elixir_libraries;
7mod module_loader;
8mod native_file_copier;
9pub mod package_compiler;
10mod package_loader;
11mod project_compiler;
12mod telemetry;
13
14#[cfg(test)]
15mod tests;
16
17pub use self::package_compiler::PackageCompiler;
18pub use self::package_loader::StaleTracker;
19pub use self::project_compiler::{Built, Options, ProjectCompiler};
20pub use self::telemetry::{NullTelemetry, Telemetry};
21
22use crate::ast::{
23 self, CallArg, CustomType, DefinitionLocation, TypeAst, TypedArg, TypedClauseGuard,
24 TypedConstant, TypedCustomType, TypedDefinitions, TypedExpr, TypedFunction, TypedImport,
25 TypedModuleConstant, TypedPattern, TypedRecordConstructor, TypedStatement, TypedTypeAlias,
26};
27use crate::reference;
28use crate::type_::{Type, TypedCallArg};
29use crate::{
30 ast::{Definition, SrcSpan, TypedModule},
31 config::{self, PackageConfig},
32 erlang,
33 error::{Error, FileIoAction, FileKind},
34 io::OutputFile,
35 parse::extra::{Comment, ModuleExtra},
36 type_,
37};
38use camino::Utf8PathBuf;
39use clap::ValueEnum;
40use ecow::EcoString;
41use itertools::Itertools;
42use serde::{Deserialize, Serialize};
43use std::fmt::{Debug, Display};
44use std::sync::Arc;
45use std::time::SystemTime;
46use std::{collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, process};
47use strum::{Display, EnumIter, EnumString, EnumVariantNames, VariantNames};
48use vec1::Vec1;
49
50#[derive(
51 Debug,
52 Serialize,
53 Deserialize,
54 ValueEnum,
55 EnumString,
56 EnumVariantNames,
57 EnumIter,
58 Clone,
59 Copy,
60 PartialEq,
61 Eq,
62)]
63#[strum(serialize_all = "lowercase")]
64#[clap(rename_all = "lower")]
65#[serde(rename_all = "lowercase")]
66pub enum Target {
67 #[strum(serialize = "erl")]
68 #[serde(alias = "erl")]
69 #[clap(alias = "erl")]
70 Erlang,
71 #[strum(serialize = "js")]
72 #[serde(alias = "js")]
73 #[clap(alias = "js")]
74 JavaScript,
75}
76
77impl Target {
78 pub fn as_presentable_str(&self) -> &str {
79 match self {
80 Target::Erlang => "Erlang",
81 Target::JavaScript => "JavaScript",
82 }
83 }
84
85 pub fn variant_strings() -> Vec<EcoString> {
86 Self::VARIANTS.iter().map(|s| (*s).into()).collect()
87 }
88
89 /// Returns `true` if the target is [`JavaScript`].
90 ///
91 /// [`JavaScript`]: Target::JavaScript
92 #[must_use]
93 pub fn is_javascript(&self) -> bool {
94 matches!(self, Self::JavaScript)
95 }
96
97 /// Returns `true` if the target is [`Erlang`].
98 ///
99 /// [`Erlang`]: Target::Erlang
100 #[must_use]
101 pub fn is_erlang(&self) -> bool {
102 matches!(self, Self::Erlang)
103 }
104}
105
106#[derive(Debug, PartialEq, Eq, Clone, Copy)]
107/// This is used to skip compiling the root package when running the main
108/// function coming from a dependency. This way a dependency can be run even
109/// there's compilation errors in the root package.
110///
111pub enum Compile {
112 /// The default compiler behaviour, compile all packages.
113 ///
114 All,
115 /// Only compile the dependency packages, skipping the root package.
116 ///
117 DepsOnly,
118}
119
120#[derive(Debug, PartialEq, Eq, Clone, Copy)]
121pub enum Codegen {
122 All,
123 DepsOnly,
124 None,
125}
126
127impl Codegen {
128 fn should_codegen(&self, is_root_package: bool) -> bool {
129 match self {
130 Codegen::All => true,
131 Codegen::DepsOnly => !is_root_package,
132 Codegen::None => false,
133 }
134 }
135}
136
137#[derive(
138 Debug,
139 Serialize,
140 Deserialize,
141 EnumString,
142 EnumVariantNames,
143 ValueEnum,
144 Clone,
145 Copy,
146 PartialEq,
147 Eq,
148)]
149#[clap(rename_all = "lower")]
150#[serde(rename_all = "lowercase")]
151#[strum(serialize_all = "lowercase")]
152pub enum Runtime {
153 #[strum(serialize = "node")]
154 #[serde(alias = "node")]
155 #[clap(alias = "node")]
156 NodeJs,
157 Deno,
158 Bun,
159}
160
161impl Runtime {
162 pub fn as_presentable_str(&self) -> &str {
163 match self {
164 Runtime::NodeJs => "NodeJS",
165 Runtime::Deno => "Deno",
166 Runtime::Bun => "Bun",
167 }
168 }
169}
170
171impl Default for Runtime {
172 fn default() -> Self {
173 Self::NodeJs
174 }
175}
176
177#[derive(Debug)]
178pub enum TargetCodegenConfiguration {
179 JavaScript {
180 emit_typescript_definitions: bool,
181 emit_source_maps: bool,
182 prelude_location: Utf8PathBuf,
183 },
184 Erlang {
185 app_file: Option<ErlangAppCodegenConfiguration>,
186 },
187}
188
189impl TargetCodegenConfiguration {
190 pub fn target(&self) -> Target {
191 match self {
192 Self::JavaScript { .. } => Target::JavaScript,
193 Self::Erlang { .. } => Target::Erlang,
194 }
195 }
196}
197
198#[derive(Debug)]
199pub struct ErlangAppCodegenConfiguration {
200 pub include_dev_deps: bool,
201 /// Some packages have a different OTP application name than their package
202 /// name, as rebar3 (and Mix?) support this. The .app file must use the OTP
203 /// name, not the package name.
204 pub package_name_overrides: HashMap<EcoString, EcoString>,
205}
206
207#[derive(
208 Debug,
209 Serialize,
210 Deserialize,
211 Display,
212 EnumString,
213 EnumVariantNames,
214 EnumIter,
215 Clone,
216 Copy,
217 PartialEq,
218)]
219#[strum(serialize_all = "lowercase")]
220pub enum Mode {
221 Dev,
222 Prod,
223 Lsp,
224}
225
226impl Mode {
227 /// Returns `true` if the mode includes development code.
228 ///
229 pub fn includes_dev_code(&self) -> bool {
230 match self {
231 Self::Dev | Self::Lsp => true,
232 Self::Prod => false,
233 }
234 }
235
236 pub fn includes_dev_dependencies(&self) -> bool {
237 match self {
238 Mode::Dev | Mode::Lsp => true,
239 Mode::Prod => false,
240 }
241 }
242}
243
244#[test]
245fn mode_includes_dev_code() {
246 assert!(Mode::Dev.includes_dev_code());
247 assert!(Mode::Lsp.includes_dev_code());
248 assert!(!Mode::Prod.includes_dev_code());
249}
250
251#[derive(Debug)]
252pub struct Package {
253 pub config: PackageConfig,
254 pub modules: Vec<Module>,
255 pub cached_module_names: Vec<EcoString>,
256}
257
258impl Package {
259 pub fn attach_doc_and_module_comments(&mut self) {
260 for mut module in &mut self.modules {
261 module.attach_doc_and_module_comments();
262 }
263 }
264
265 pub fn into_modules_hashmap(self) -> HashMap<String, Module> {
266 self.modules
267 .into_iter()
268 .map(|m| (m.name.to_string(), m))
269 .collect()
270 }
271}
272
273#[derive(Debug)]
274pub struct Module {
275 pub name: EcoString,
276 pub code: EcoString,
277 pub mtime: SystemTime,
278 pub input_path: Utf8PathBuf,
279 pub origin: Origin,
280 pub ast: TypedModule,
281 pub extra: ModuleExtra,
282 pub dependencies: Vec<(EcoString, SrcSpan)>,
283}
284
285#[derive(Debug)]
286/// A data structure used to store all definitions in a single homogeneous
287/// vector and sort them by their position in order to attach to each the right
288/// documentation.
289///
290enum DocumentableDefinition<'a> {
291 Constant(&'a mut TypedModuleConstant),
292 TypeAlias(&'a mut TypedTypeAlias),
293 CustomType(&'a mut TypedCustomType),
294 Function(&'a mut TypedFunction),
295 Import(&'a mut TypedImport),
296}
297
298impl<'a> DocumentableDefinition<'a> {
299 pub fn location(&self) -> SrcSpan {
300 match self {
301 Self::Constant(module_constant) => module_constant.location,
302 Self::TypeAlias(type_alias) => type_alias.location,
303 Self::CustomType(custom_type) => custom_type.location,
304 Self::Function(function) => function.location,
305 Self::Import(import) => import.location,
306 }
307 }
308
309 pub fn put_doc(&mut self, new_documentation: (u32, EcoString)) {
310 match self {
311 Self::Import(_import) => (),
312 Self::Function(function) => {
313 let _ = function.documentation.replace(new_documentation);
314 }
315 Self::TypeAlias(type_alias) => {
316 let _ = type_alias.documentation.replace(new_documentation);
317 }
318 Self::CustomType(custom_type) => {
319 let _ = custom_type.documentation.replace(new_documentation);
320 }
321 Self::Constant(constant) => {
322 let _ = constant.documentation.replace(new_documentation);
323 }
324 }
325 }
326}
327
328impl Module {
329 pub fn erlang_name(&self) -> EcoString {
330 module_erlang_name(&self.name)
331 }
332
333 pub fn compiled_erlang_path(&self) -> Utf8PathBuf {
334 let mut path = Utf8PathBuf::from(&module_erlang_name(&self.name));
335 assert!(path.set_extension("erl"), "Couldn't set file extension");
336 path
337 }
338
339 pub fn find_node(&self, byte_index: u32) -> Option<Located<'_>> {
340 self.ast.find_node(byte_index)
341 }
342
343 pub fn attach_doc_and_module_comments(&mut self) {
344 // Module Comments
345 self.ast.documentation = self
346 .extra
347 .module_comments
348 .iter()
349 .map(|span| Comment::from((span, self.code.as_str())).content.into())
350 .collect();
351
352 self.ast.type_info.documentation = self.ast.documentation.clone();
353
354 // Order definitions to avoid misassociating doc comments after the
355 // order has changed during compilation.
356 let TypedDefinitions {
357 imports,
358 constants,
359 custom_types,
360 type_aliases,
361 functions,
362 } = &mut self.ast.definitions;
363
364 let mut definitions = ((imports.iter_mut()).map(DocumentableDefinition::Import))
365 .chain((constants.iter_mut()).map(DocumentableDefinition::Constant))
366 .chain((custom_types.iter_mut()).map(DocumentableDefinition::CustomType))
367 .chain((type_aliases.iter_mut()).map(DocumentableDefinition::TypeAlias))
368 .chain((functions.iter_mut()).map(DocumentableDefinition::Function))
369 .sorted_by_key(|definition| definition.location())
370 .collect_vec();
371
372 // Doc Comments
373 let mut doc_comments = self.extra.doc_comments.iter().peekable();
374 for definition in &mut definitions {
375 let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before(
376 &mut doc_comments,
377 &self.extra,
378 definition.location().start,
379 &self.code,
380 );
381 if !docs.is_empty() {
382 let doc = docs.join("\n").into();
383 definition.put_doc((docs_start, doc));
384 }
385
386 if let DocumentableDefinition::CustomType(CustomType { constructors, .. }) = definition
387 {
388 for constructor in constructors {
389 let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before(
390 &mut doc_comments,
391 &self.extra,
392 constructor.location.start,
393 &self.code,
394 );
395 if !docs.is_empty() {
396 let doc = docs.join("\n").into();
397 constructor.put_doc((docs_start, doc));
398 }
399
400 for argument in constructor.arguments.iter_mut() {
401 let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before(
402 &mut doc_comments,
403 &self.extra,
404 argument.location.start,
405 &self.code,
406 );
407 if !docs.is_empty() {
408 let doc = docs.join("\n").into();
409 argument.put_doc((docs_start, doc));
410 }
411 }
412 }
413 }
414 }
415 }
416}
417
418pub fn module_erlang_name(gleam_name: &EcoString) -> EcoString {
419 gleam_name.replace("/", "@")
420}
421
422#[derive(Debug, Clone, PartialEq)]
423pub struct UnqualifiedImport<'a> {
424 pub name: &'a EcoString,
425 pub module: &'a EcoString,
426 pub is_type: bool,
427 pub location: &'a SrcSpan,
428}
429
430/// The position of a located expression. Used to determine extra context,
431/// such as whether to provide label completions if the expression is in
432/// argument position.
433#[derive(Debug, Clone, PartialEq)]
434pub enum ExpressionPosition<'a> {
435 Expression,
436 ArgumentOrLabel {
437 called_function: &'a TypedExpr,
438 function_arguments: &'a [TypedCallArg],
439 },
440 /// This is for an expression that is used in a record update spread:
441 /// ```gleam
442 /// Wibble(..wibble, a: 1)
443 /// // ^^^^^^^^ This!
444 /// ```
445 UpdatedRecord {
446 /// These are all the record fields that are not being updated in the
447 /// update.
448 unchanged_record_fields: Vec<(EcoString, Arc<Type>)>,
449 },
450}
451
452#[derive(Debug, Clone, PartialEq)]
453pub enum Located<'a> {
454 Pattern(&'a TypedPattern),
455 PatternSpread {
456 spread_location: SrcSpan,
457 pattern: &'a TypedPattern,
458 },
459 // A prefix alias or a suffix variable defined in a string prefix pattern:
460 // "prefix" as alias <> suffix
461 StringPrefixPatternVariable {
462 location: SrcSpan,
463 name: &'a EcoString,
464 },
465 Statement(&'a TypedStatement),
466 Expression {
467 expression: &'a TypedExpr,
468 position: ExpressionPosition<'a>,
469 },
470 VariantConstructorDefinition(&'a TypedRecordConstructor),
471 FunctionBody(&'a TypedFunction),
472 Arg(&'a TypedArg),
473 Annotation {
474 ast: &'a TypeAst,
475 type_: std::sync::Arc<Type>,
476 },
477 TypeVariable {
478 name: EcoString,
479 location: SrcSpan,
480 },
481 UnqualifiedImport(UnqualifiedImport<'a>),
482 /// The label of a labelled argument in a function call.
483 Label {
484 location: SrcSpan,
485 /// The type of the labelled argument's value (used for hover).
486 field_type: std::sync::Arc<Type>,
487 },
488 /// A record field label at its definition in a custom type variant.
489 RecordLabelDefinition {
490 location: SrcSpan,
491 /// The type of the field (used for hover).
492 field_type: std::sync::Arc<Type>,
493 label: EcoString,
494 /// The name of the custom type this field belongs to. The type is
495 /// being defined in the module being analysed, so only its name is
496 /// needed.
497 type_name: EcoString,
498 },
499 /// A record field label used in a record constructor call or pattern, or
500 /// in a record update.
501 RecordLabelUsage {
502 location: SrcSpan,
503 /// The type of the field's value (used for hover).
504 field_type: std::sync::Arc<Type>,
505 label: EcoString,
506 /// The record type the field belongs to.
507 record_type: std::sync::Arc<Type>,
508 /// The name of the variant the field was used with.
509 variant: EcoString,
510 },
511 /// The label of a record field access: `record.label`.
512 RecordAccessLabel {
513 location: SrcSpan,
514 /// The type of the field (used for hover).
515 field_type: std::sync::Arc<Type>,
516 label: EcoString,
517 /// The record type the field belongs to.
518 record_type: std::sync::Arc<Type>,
519 /// The documentation of the field (used for hover).
520 documentation: Option<EcoString>,
521 },
522 ModuleName {
523 location: SrcSpan,
524 module_name: EcoString,
525 module_alias: EcoString,
526 layer: ast::Layer,
527 },
528 Constant(&'a TypedConstant),
529 ClauseGuard(&'a TypedClauseGuard),
530
531 // A module's top level definitions
532 ModuleFunction(&'a TypedFunction),
533 ModuleConstant(&'a TypedModuleConstant),
534 ModuleImport(&'a TypedImport),
535 ModuleCustomType(&'a TypedCustomType),
536 ModuleTypeAlias(&'a TypedTypeAlias),
537}
538
539impl<'a> Located<'a> {
540 // Looks up the type constructor for the given type and then create the location.
541 fn type_location(
542 &self,
543 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
544 type_: std::sync::Arc<Type>,
545 ) -> Option<DefinitionLocation> {
546 type_constructor_from_modules(importable_modules, type_).map(|t| DefinitionLocation {
547 module: Some(t.module.clone()),
548 span: t.origin,
549 })
550 }
551
552 /// Looks up the location at which a record field label was defined, using
553 /// the label definitions gathered during analysis.
554 fn label_definition_location(
555 &self,
556 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
557 record_type: &Arc<Type>,
558 label: &EcoString,
559 variant: Option<&EcoString>,
560 ) -> Option<DefinitionLocation> {
561 let (module_name, type_name) = record_type.named_type_name()?;
562 let module = importable_modules.get(&module_name)?;
563 let key = reference::RecordLabel {
564 type_module: module_name.clone(),
565 type_name,
566 label: label.clone(),
567 };
568 let definitions = module.references.label_definitions.get(&key)?;
569 // A label can be defined in multiple variants of the same type. If we
570 // know which variant the label was used with we jump to its
571 // definition in that variant. Otherwise the label comes from a
572 // `record.field` access, which works across all the variants defining
573 // it, so we jump to the first definition.
574 let definition = match variant {
575 Some(variant) => definitions
576 .iter()
577 .find(|definition| &definition.variant == variant)?,
578 None => definitions.first()?,
579 };
580 Some(DefinitionLocation {
581 module: Some(module_name),
582 span: definition.location,
583 })
584 }
585
586 pub fn definition_location(
587 &self,
588 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
589 ) -> Option<DefinitionLocation> {
590 match self {
591 Self::PatternSpread { .. } => None,
592 Self::Pattern(pattern) => pattern.definition_location(),
593 Self::StringPrefixPatternVariable { location, .. } => Some(DefinitionLocation {
594 module: None,
595 span: *location,
596 }),
597 Self::Statement(statement) => statement.definition_location(),
598 Self::FunctionBody(statement) => None,
599 Self::Expression { expression, .. } => expression.definition_location(),
600
601 Self::ModuleImport(import) => Some(DefinitionLocation {
602 module: Some(import.module.clone()),
603 span: SrcSpan { start: 0, end: 0 },
604 }),
605 Self::ModuleConstant(constant) => Some(DefinitionLocation {
606 module: None,
607 span: constant.location,
608 }),
609 Self::ModuleCustomType(custom_type) => Some(DefinitionLocation {
610 module: None,
611 span: custom_type.location,
612 }),
613 Self::ModuleFunction(function) => Some(DefinitionLocation {
614 module: None,
615 span: function.location,
616 }),
617 Self::ModuleTypeAlias(type_alias) => Some(DefinitionLocation {
618 module: None,
619 span: type_alias.location,
620 }),
621
622 Self::VariantConstructorDefinition(record) => Some(DefinitionLocation {
623 module: None,
624 span: record.location,
625 }),
626 Self::UnqualifiedImport(UnqualifiedImport {
627 module,
628 name,
629 is_type,
630 ..
631 }) => importable_modules.get(*module).and_then(|m| {
632 if *is_type {
633 m.types.get(*name).map(|t| DefinitionLocation {
634 module: Some((*module).clone()),
635 span: t.origin,
636 })
637 } else {
638 m.values.get(*name).map(|v| DefinitionLocation {
639 module: Some((*module).clone()),
640 span: v.definition_location().span,
641 })
642 }
643 }),
644 Self::Arg(_) => None,
645 Self::Annotation { type_, .. } => self.type_location(importable_modules, type_.clone()),
646 Self::Label { .. } => None,
647 Self::RecordLabelUsage {
648 record_type,
649 label,
650 variant,
651 ..
652 } => self.label_definition_location(
653 importable_modules,
654 record_type,
655 label,
656 Some(variant),
657 ),
658 Self::RecordAccessLabel {
659 record_type, label, ..
660 } => self.label_definition_location(importable_modules, record_type, label, None),
661 // Already at the definition; go-to-definition jumps to itself.
662 Self::RecordLabelDefinition { location, .. } => Some(DefinitionLocation {
663 module: None,
664 span: *location,
665 }),
666 Self::TypeVariable { .. } => None,
667 Self::ModuleName { module_name, .. } => Some(DefinitionLocation {
668 module: Some(module_name.clone()),
669 span: SrcSpan::new(0, 0),
670 }),
671 Self::Constant(constant) => constant.definition_location(),
672 Self::ClauseGuard(guard) => guard.definition_location(),
673 }
674 }
675
676 pub(crate) fn type_(&self) -> Option<Arc<Type>> {
677 match self {
678 Located::Pattern(pattern) => Some(pattern.type_()),
679 Located::StringPrefixPatternVariable { .. } => Some(type_::string()),
680 Located::Statement(statement) => Some(statement.type_()),
681 Located::Expression { expression, .. } => Some(expression.type_()),
682 Located::Arg(arg) => Some(arg.type_.clone()),
683 Located::Label { field_type, .. }
684 | Located::RecordLabelDefinition { field_type, .. }
685 | Located::RecordLabelUsage { field_type, .. }
686 | Located::RecordAccessLabel { field_type, .. } => Some(field_type.clone()),
687 Located::Annotation { type_, .. } => Some(type_.clone()),
688 Located::Constant(constant) => Some(constant.type_()),
689 Located::ClauseGuard(guard) => Some(guard.type_()),
690
691 Located::PatternSpread { .. }
692 | Located::ModuleConstant(_)
693 | Located::ModuleCustomType(_)
694 | Located::ModuleFunction(_)
695 | Located::ModuleImport(_)
696 | Located::ModuleTypeAlias(_)
697 | Located::VariantConstructorDefinition(_)
698 | Located::FunctionBody(_)
699 | Located::UnqualifiedImport(_)
700 | Located::ModuleName { .. }
701 | Located::TypeVariable { .. } => None,
702 }
703 }
704
705 pub fn type_definition_locations(
706 &self,
707 importable_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
708 ) -> Option<Vec<DefinitionLocation>> {
709 let type_ = self.type_()?;
710 Some(type_to_definition_locations(type_, importable_modules))
711 }
712}
713
714/// Returns the locations of all the types that one could reach starting from
715/// the given type (included). This includes all types that are part of a
716/// tuple/function type or that are used as args in a named type.
717///
718/// For example, given this type `Dict(Int, #(Wibble, Wobble))` all the
719/// "reachable" include: `Dict`, `Int`, `Wibble` and `Wobble`.
720///
721/// This is what powers the "go to type definition" capability of the language
722/// server.
723///
724fn type_to_definition_locations<'a>(
725 type_: Arc<Type>,
726 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
727) -> Vec<DefinitionLocation> {
728 match type_.as_ref() {
729 // For named types we start with the location of the named type itself
730 // followed by the locations of all types they reference in their args.
731 //
732 // For example with a `Dict(Wibble, Wobble)` we'd start with the
733 // definition of `Dict`, followed by the definition of `Wibble` and
734 // `Wobble`.
735 //
736 Type::Named {
737 module,
738 name,
739 arguments,
740 ..
741 } => {
742 let Some(module) = importable_modules.get(module) else {
743 return vec![];
744 };
745
746 let Some(type_) = module.get_importable_type(&name) else {
747 return vec![];
748 };
749
750 let mut locations = vec![DefinitionLocation {
751 module: Some(module.name.clone()),
752 span: type_.origin,
753 }];
754 for argument in arguments {
755 locations.extend(type_to_definition_locations(
756 argument.clone(),
757 importable_modules,
758 ));
759 }
760 locations
761 }
762
763 // For fn types we just get the locations of their arguments and return
764 // type.
765 //
766 Type::Fn { arguments, return_ } => arguments
767 .iter()
768 .flat_map(|argument| type_to_definition_locations(argument.clone(), importable_modules))
769 .chain(type_to_definition_locations(
770 return_.clone(),
771 importable_modules,
772 ))
773 .collect_vec(),
774
775 // In case of a var we just follow it and get the locations of the type
776 // it points to.
777 //
778 Type::Var { type_ } => match type_.borrow().clone() {
779 type_::TypeVar::Unbound { .. } | type_::TypeVar::Generic { .. } => vec![],
780 type_::TypeVar::Link { type_ } => {
781 type_to_definition_locations(type_, importable_modules)
782 }
783 },
784
785 // In case of tuples we get the locations of the wrapped types.
786 //
787 Type::Tuple { elements } => elements
788 .iter()
789 .flat_map(|element| type_to_definition_locations(element.clone(), importable_modules))
790 .collect_vec(),
791 }
792}
793
794// Looks up the type constructor for the given type
795pub fn type_constructor_from_modules(
796 importable_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
797 type_: std::sync::Arc<Type>,
798) -> Option<&type_::TypeConstructor> {
799 let type_ = type_::collapse_links(type_);
800 match type_.as_ref() {
801 Type::Named { name, module, .. } => importable_modules
802 .get(module)
803 .and_then(|i| i.types.get(name)),
804 _ => None,
805 }
806}
807
808#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
809pub enum Origin {
810 Src,
811 Test,
812 Dev,
813}
814
815impl Origin {
816 /// Returns `true` if the origin is [`Src`].
817 ///
818 /// [`Src`]: Origin::Src
819 #[must_use]
820 pub fn is_src(&self) -> bool {
821 matches!(self, Self::Src)
822 }
823
824 /// Returns `true` if the origin is [`Test`].
825 ///
826 /// [`Test`]: Origin::Test
827 #[must_use]
828 pub fn is_test(&self) -> bool {
829 matches!(self, Self::Test)
830 }
831
832 /// Returns `true` if the origin is [`Dev`].
833 ///
834 /// [`Dev`]: Origin::Dev
835 #[must_use]
836 pub fn is_dev(&self) -> bool {
837 matches!(self, Self::Dev)
838 }
839
840 /// Name of the folder containing the origin.
841 #[must_use]
842 pub fn folder_name(&self) -> &str {
843 match self {
844 Origin::Src => "src",
845 Origin::Test => "test",
846 Origin::Dev => "dev",
847 }
848 }
849}
850
851fn doc_comments_before<'a>(
852 doc_comments_spans: &mut Peekable<impl Iterator<Item = &'a SrcSpan>>,
853 extra: &ModuleExtra,
854 byte: u32,
855 src: &'a str,
856) -> (u32, Vec<&'a str>) {
857 let mut comments = vec![];
858 let mut comment_start = u32::MAX;
859 while let Some(SrcSpan { start, end }) = doc_comments_spans.peek() {
860 if start > &byte {
861 break;
862 }
863 if extra.has_comment_between(*end, byte) {
864 // We ignore doc comments that come before a regular comment.
865 _ = doc_comments_spans.next();
866 continue;
867 }
868 let comment = doc_comments_spans
869 .next()
870 .expect("Comment before accessing next span");
871
872 if comment.start < comment_start {
873 comment_start = comment.start;
874 }
875
876 comments.push(Comment::from((comment, src)).content)
877 }
878 (comment_start, comments)
879}
880
881#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
882pub struct SourceFingerprint(u64);
883
884impl SourceFingerprint {
885 pub fn new(source: &str) -> Self {
886 SourceFingerprint(xxhash_rust::xxh3::xxh3_64(source.as_bytes()))
887 }
888
889 pub fn to_numerical_string(&self) -> String {
890 self.0.to_string()
891 }
892}
893
894/// Like a `Result`, but the operation can partially succeed or fail.
895///
896#[derive(Debug)]
897pub enum Outcome<T, E> {
898 /// The operation was totally succesful.
899 Ok(T),
900
901 /// The operation was partially successful but there were problems.
902 PartialFailure(T, E),
903
904 /// The operation was entirely unsuccessful.
905 TotalFailure(E),
906}
907
908impl<T, E> Outcome<T, E>
909where
910 E: Debug,
911{
912 #[cfg(test)]
913 /// Panic if there's any errors
914 pub fn unwrap(self) -> T {
915 match self {
916 Outcome::Ok(t) => t,
917 Outcome::PartialFailure(_, errors) => panic!("Error: {:?}", errors),
918 Outcome::TotalFailure(error) => panic!("Error: {:?}", error),
919 }
920 }
921
922 /// Panic if there's any errors
923 pub fn expect(self, e: &'static str) -> T {
924 match self {
925 Outcome::Ok(t) => t,
926 Outcome::PartialFailure(_, errors) => panic!("{e}: {:?}", errors),
927 Outcome::TotalFailure(error) => panic!("{e}: {:?}", error),
928 }
929 }
930
931 pub fn into_result(self) -> Result<T, E> {
932 match self {
933 Outcome::Ok(t) => Ok(t),
934 Outcome::PartialFailure(_, e) | Outcome::TotalFailure(e) => Err(e),
935 }
936 }
937
938 pub fn map<T2>(self, f: impl FnOnce(T) -> T2) -> Outcome<T2, E> {
939 match self {
940 Outcome::Ok(t) => Outcome::Ok(f(t)),
941 Outcome::PartialFailure(t, e) => Outcome::PartialFailure(f(t), e),
942 Outcome::TotalFailure(e) => Outcome::TotalFailure(e),
943 }
944 }
945}