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