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