Fork of daniellemaywood.uk/gleam — Wasm codegen work
31 kB
983 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 is_type: bool,
428 pub location: &'a SrcSpan,
429 /// The location excluding the potential `as ...` clause, or the `type` keyword.
430 /// For example, in `type Wibble as Wobble`, it covers `Wibble`.
431 pub imported_name_location: &'a SrcSpan,
432 pub as_name: Option<&'a EcoString>,
433}
434
435impl<'a> UnqualifiedImport<'a> {
436 /// If the import is aliased, it is the start of the alias. Otherwise, it is
437 /// the start of the imported name.
438 ///
439 /// For example, in `type Wibble as Wobble`, the used name will start at
440 /// `Wobble`. In `type Wibble`, it will start at `Wibble`.
441 pub fn used_name_start(&self) -> u32 {
442 match self.as_name {
443 // For aliases, the location span will cover the whole of `type
444 // Wibble as Wobble`.
445 // So, the used name will start 6 chars (length of `Wobble`) before
446 // the span ends.
447 Some(as_name) => self.location.end - as_name.len() as u32,
448 // For non-aliased imports, use the start of the imported name
449 // location. It covers `Wibble` in `type Wibble as Wobble`.
450 None => self.imported_name_location.start,
451 }
452 }
453
454 pub fn name_kind(&self) -> Named {
455 let is_upname = match self.name.chars().next() {
456 Some(c) => c.is_uppercase(),
457 None => false,
458 };
459
460 if is_upname {
461 Named::CustomTypeVariant
462 } else {
463 Named::Function
464 }
465 }
466}
467
468/// The position of a located expression. Used to determine extra context,
469/// such as whether to provide label completions if the expression is in
470/// argument position.
471#[derive(Debug, Clone, PartialEq)]
472pub enum ExpressionPosition<'a> {
473 Expression,
474 ArgumentOrLabel {
475 called_function: &'a TypedExpr,
476 function_arguments: &'a [TypedCallArg],
477 },
478 /// This is for an expression that is used in a record update spread:
479 /// ```gleam
480 /// Wibble(..wibble, a: 1)
481 /// // ^^^^^^^^ This!
482 /// ```
483 UpdatedRecord {
484 /// These are all the record fields that are not being updated in the
485 /// update.
486 unchanged_record_fields: Vec<(EcoString, Arc<Type>)>,
487 },
488}
489
490#[derive(Debug, Clone, PartialEq)]
491pub enum Located<'a> {
492 Pattern(&'a TypedPattern),
493 PatternSpread {
494 spread_location: SrcSpan,
495 pattern: &'a TypedPattern,
496 },
497 // A prefix alias or a suffix variable defined in a string prefix pattern:
498 // "prefix" as alias <> suffix
499 StringPrefixPatternVariable {
500 location: SrcSpan,
501 name: &'a EcoString,
502 },
503 Statement(&'a TypedStatement),
504 Expression {
505 expression: &'a TypedExpr,
506 position: ExpressionPosition<'a>,
507 },
508 VariantConstructorDefinition(&'a TypedRecordConstructor),
509 FunctionBody(&'a TypedFunction),
510 Arg(&'a TypedArg),
511 Annotation {
512 ast: &'a TypeAst,
513 type_: std::sync::Arc<Type>,
514 },
515 TypeVariable {
516 name: EcoString,
517 location: SrcSpan,
518 },
519 UnqualifiedImport(UnqualifiedImport<'a>),
520 /// The label of a labelled argument in a function call.
521 Label {
522 location: SrcSpan,
523 /// The type of the labelled argument's value (used for hover).
524 field_type: std::sync::Arc<Type>,
525 },
526 /// A record field label at its definition in a custom type variant.
527 RecordLabelDefinition {
528 location: SrcSpan,
529 /// The type of the field (used for hover).
530 field_type: std::sync::Arc<Type>,
531 label: EcoString,
532 /// The name of the custom type this field belongs to. The type is
533 /// being defined in the module being analysed, so only its name is
534 /// needed.
535 type_name: EcoString,
536 },
537 /// A record field label used in a record constructor call or pattern, or
538 /// in a record update.
539 RecordLabelUsage {
540 location: SrcSpan,
541 /// The type of the field's value (used for hover).
542 field_type: std::sync::Arc<Type>,
543 label: EcoString,
544 /// The record type the field belongs to.
545 record_type: std::sync::Arc<Type>,
546 /// The name of the variant the field was used with.
547 variant: EcoString,
548 },
549 /// The label of a record field access: `record.label`.
550 RecordAccessLabel {
551 location: SrcSpan,
552 /// The type of the field (used for hover).
553 field_type: std::sync::Arc<Type>,
554 label: EcoString,
555 /// The record type the field belongs to.
556 record_type: std::sync::Arc<Type>,
557 /// The documentation of the field (used for hover).
558 documentation: Option<EcoString>,
559 },
560 ModuleName {
561 location: SrcSpan,
562 module_name: EcoString,
563 module_alias: EcoString,
564 layer: ast::Layer,
565 },
566 Constant(&'a TypedConstant),
567 ClauseGuard(&'a TypedClauseGuard),
568
569 // A module's top level definitions
570 ModuleFunction(&'a TypedFunction),
571 ModuleConstant(&'a TypedModuleConstant),
572 ModuleImport(&'a TypedImport),
573 ModuleCustomType(&'a TypedCustomType),
574 ModuleTypeAlias(&'a TypedTypeAlias),
575}
576
577impl<'a> Located<'a> {
578 // Looks up the type constructor for the given type and then create the location.
579 fn type_location(
580 &self,
581 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
582 type_: std::sync::Arc<Type>,
583 ) -> Option<DefinitionLocation> {
584 type_constructor_from_modules(importable_modules, type_).map(|t| DefinitionLocation {
585 module: Some(t.module.clone()),
586 span: t.origin,
587 })
588 }
589
590 /// Looks up the location at which a record field label was defined, using
591 /// the label definitions gathered during analysis.
592 fn label_definition_location(
593 &self,
594 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
595 record_type: &Arc<Type>,
596 label: &EcoString,
597 variant: Option<&EcoString>,
598 ) -> Option<DefinitionLocation> {
599 let (module_name, type_name) = record_type.named_type_name()?;
600 let module = importable_modules.get(&module_name)?;
601 let key = reference::RecordLabel {
602 type_module: module_name.clone(),
603 type_name,
604 label: label.clone(),
605 };
606 let definitions = module.references.label_definitions.get(&key)?;
607 // A label can be defined in multiple variants of the same type. If we
608 // know which variant the label was used with we jump to its
609 // definition in that variant. Otherwise the label comes from a
610 // `record.field` access, which works across all the variants defining
611 // it, so we jump to the first definition.
612 let definition = match variant {
613 Some(variant) => definitions
614 .iter()
615 .find(|definition| &definition.variant == variant)?,
616 None => definitions.first()?,
617 };
618 Some(DefinitionLocation {
619 module: Some(module_name),
620 span: definition.location,
621 })
622 }
623
624 pub fn definition_location(
625 &self,
626 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
627 ) -> Option<DefinitionLocation> {
628 match self {
629 Self::PatternSpread { .. } => None,
630 Self::Pattern(pattern) => pattern.definition_location(),
631 Self::StringPrefixPatternVariable { location, .. } => Some(DefinitionLocation {
632 module: None,
633 span: *location,
634 }),
635 Self::Statement(statement) => statement.definition_location(),
636 Self::FunctionBody(statement) => None,
637 Self::Expression { expression, .. } => expression.definition_location(),
638
639 Self::ModuleImport(import) => Some(DefinitionLocation {
640 module: Some(import.module.clone()),
641 span: SrcSpan { start: 0, end: 0 },
642 }),
643 Self::ModuleConstant(constant) => Some(DefinitionLocation {
644 module: None,
645 span: constant.location,
646 }),
647 Self::ModuleCustomType(custom_type) => Some(DefinitionLocation {
648 module: None,
649 span: custom_type.location,
650 }),
651 Self::ModuleFunction(function) => Some(DefinitionLocation {
652 module: None,
653 span: function.location,
654 }),
655 Self::ModuleTypeAlias(type_alias) => Some(DefinitionLocation {
656 module: None,
657 span: type_alias.location,
658 }),
659
660 Self::VariantConstructorDefinition(record) => Some(DefinitionLocation {
661 module: None,
662 span: record.location,
663 }),
664 Self::UnqualifiedImport(UnqualifiedImport {
665 module,
666 name,
667 is_type,
668 ..
669 }) => importable_modules.get(*module).and_then(|m| {
670 if *is_type {
671 m.types.get(*name).map(|t| DefinitionLocation {
672 module: Some((*module).clone()),
673 span: t.origin,
674 })
675 } else {
676 m.values.get(*name).map(|v| DefinitionLocation {
677 module: Some((*module).clone()),
678 span: v.definition_location().span,
679 })
680 }
681 }),
682 Self::Arg(_) => None,
683 Self::Annotation { type_, .. } => self.type_location(importable_modules, type_.clone()),
684 Self::Label { .. } => None,
685 Self::RecordLabelUsage {
686 record_type,
687 label,
688 variant,
689 ..
690 } => self.label_definition_location(
691 importable_modules,
692 record_type,
693 label,
694 Some(variant),
695 ),
696 Self::RecordAccessLabel {
697 record_type, label, ..
698 } => self.label_definition_location(importable_modules, record_type, label, None),
699 // Already at the definition; go-to-definition jumps to itself.
700 Self::RecordLabelDefinition { location, .. } => Some(DefinitionLocation {
701 module: None,
702 span: *location,
703 }),
704 Self::TypeVariable { .. } => None,
705 Self::ModuleName { module_name, .. } => Some(DefinitionLocation {
706 module: Some(module_name.clone()),
707 span: SrcSpan::new(0, 0),
708 }),
709 Self::Constant(constant) => constant.definition_location(),
710 Self::ClauseGuard(guard) => guard.definition_location(),
711 }
712 }
713
714 pub(crate) fn type_(&self) -> Option<Arc<Type>> {
715 match self {
716 Located::Pattern(pattern) => Some(pattern.type_()),
717 Located::StringPrefixPatternVariable { .. } => Some(type_::string()),
718 Located::Statement(statement) => Some(statement.type_()),
719 Located::Expression { expression, .. } => Some(expression.type_()),
720 Located::Arg(arg) => Some(arg.type_.clone()),
721 Located::Label { field_type, .. }
722 | Located::RecordLabelDefinition { field_type, .. }
723 | Located::RecordLabelUsage { field_type, .. }
724 | Located::RecordAccessLabel { field_type, .. } => Some(field_type.clone()),
725 Located::Annotation { type_, .. } => Some(type_.clone()),
726 Located::Constant(constant) => Some(constant.type_()),
727 Located::ClauseGuard(guard) => Some(guard.type_()),
728
729 Located::PatternSpread { .. }
730 | Located::ModuleConstant(_)
731 | Located::ModuleCustomType(_)
732 | Located::ModuleFunction(_)
733 | Located::ModuleImport(_)
734 | Located::ModuleTypeAlias(_)
735 | Located::VariantConstructorDefinition(_)
736 | Located::FunctionBody(_)
737 | Located::UnqualifiedImport(_)
738 | Located::ModuleName { .. }
739 | Located::TypeVariable { .. } => None,
740 }
741 }
742
743 pub fn type_definition_locations(
744 &self,
745 importable_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
746 ) -> Option<Vec<DefinitionLocation>> {
747 let type_ = self.type_()?;
748 Some(type_to_definition_locations(type_, importable_modules))
749 }
750}
751
752/// Returns the locations of all the types that one could reach starting from
753/// the given type (included). This includes all types that are part of a
754/// tuple/function type or that are used as args in a named type.
755///
756/// For example, given this type `Dict(Int, #(Wibble, Wobble))` all the
757/// "reachable" include: `Dict`, `Int`, `Wibble` and `Wobble`.
758///
759/// This is what powers the "go to type definition" capability of the language
760/// server.
761///
762fn type_to_definition_locations<'a>(
763 type_: Arc<Type>,
764 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>,
765) -> Vec<DefinitionLocation> {
766 match type_.as_ref() {
767 // For named types we start with the location of the named type itself
768 // followed by the locations of all types they reference in their args.
769 //
770 // For example with a `Dict(Wibble, Wobble)` we'd start with the
771 // definition of `Dict`, followed by the definition of `Wibble` and
772 // `Wobble`.
773 //
774 Type::Named {
775 module,
776 name,
777 arguments,
778 ..
779 } => {
780 let Some(module) = importable_modules.get(module) else {
781 return vec![];
782 };
783
784 let Some(type_) = module.get_importable_type(&name) else {
785 return vec![];
786 };
787
788 let mut locations = vec![DefinitionLocation {
789 module: Some(module.name.clone()),
790 span: type_.origin,
791 }];
792 for argument in arguments {
793 locations.extend(type_to_definition_locations(
794 argument.clone(),
795 importable_modules,
796 ));
797 }
798 locations
799 }
800
801 // For fn types we just get the locations of their arguments and return
802 // type.
803 //
804 Type::Fn { arguments, return_ } => arguments
805 .iter()
806 .flat_map(|argument| type_to_definition_locations(argument.clone(), importable_modules))
807 .chain(type_to_definition_locations(
808 return_.clone(),
809 importable_modules,
810 ))
811 .collect_vec(),
812
813 // In case of a var we just follow it and get the locations of the type
814 // it points to.
815 //
816 Type::Var { type_ } => match type_.borrow().clone() {
817 type_::TypeVar::Unbound { .. } | type_::TypeVar::Generic { .. } => vec![],
818 type_::TypeVar::Link { type_ } => {
819 type_to_definition_locations(type_, importable_modules)
820 }
821 },
822
823 // In case of tuples we get the locations of the wrapped types.
824 //
825 Type::Tuple { elements } => elements
826 .iter()
827 .flat_map(|element| type_to_definition_locations(element.clone(), importable_modules))
828 .collect_vec(),
829 }
830}
831
832// Looks up the type constructor for the given type
833pub fn type_constructor_from_modules(
834 importable_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
835 type_: std::sync::Arc<Type>,
836) -> Option<&type_::TypeConstructor> {
837 let type_ = type_::collapse_links(type_);
838 match type_.as_ref() {
839 Type::Named { name, module, .. } => importable_modules
840 .get(module)
841 .and_then(|i| i.types.get(name)),
842 _ => None,
843 }
844}
845
846#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
847pub enum Origin {
848 Src,
849 Test,
850 Dev,
851}
852
853impl Origin {
854 /// Returns `true` if the origin is [`Src`].
855 ///
856 /// [`Src`]: Origin::Src
857 #[must_use]
858 pub fn is_src(&self) -> bool {
859 matches!(self, Self::Src)
860 }
861
862 /// Returns `true` if the origin is [`Test`].
863 ///
864 /// [`Test`]: Origin::Test
865 #[must_use]
866 pub fn is_test(&self) -> bool {
867 matches!(self, Self::Test)
868 }
869
870 /// Returns `true` if the origin is [`Dev`].
871 ///
872 /// [`Dev`]: Origin::Dev
873 #[must_use]
874 pub fn is_dev(&self) -> bool {
875 matches!(self, Self::Dev)
876 }
877
878 /// Name of the folder containing the origin.
879 #[must_use]
880 pub fn folder_name(&self) -> &str {
881 match self {
882 Origin::Src => "src",
883 Origin::Test => "test",
884 Origin::Dev => "dev",
885 }
886 }
887}
888
889fn doc_comments_before<'a>(
890 doc_comments_spans: &mut Peekable<impl Iterator<Item = &'a SrcSpan>>,
891 extra: &ModuleExtra,
892 byte: u32,
893 src: &'a str,
894) -> (u32, Vec<&'a str>) {
895 let mut comments = vec![];
896 let mut comment_start = u32::MAX;
897 while let Some(SrcSpan { start, end }) = doc_comments_spans.peek() {
898 if start > &byte {
899 break;
900 }
901 if extra.has_comment_between(*end, byte) {
902 // We ignore doc comments that come before a regular comment.
903 _ = doc_comments_spans.next();
904 continue;
905 }
906 let comment = doc_comments_spans
907 .next()
908 .expect("Comment before accessing next span");
909
910 if comment.start < comment_start {
911 comment_start = comment.start;
912 }
913
914 comments.push(Comment::from((comment, src)).content)
915 }
916 (comment_start, comments)
917}
918
919#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
920pub struct SourceFingerprint(u64);
921
922impl SourceFingerprint {
923 pub fn new(source: &str) -> Self {
924 SourceFingerprint(xxhash_rust::xxh3::xxh3_64(source.as_bytes()))
925 }
926
927 pub fn to_numerical_string(&self) -> String {
928 self.0.to_string()
929 }
930}
931
932/// Like a `Result`, but the operation can partially succeed or fail.
933///
934#[derive(Debug)]
935pub enum Outcome<T, E> {
936 /// The operation was totally succesful.
937 Ok(T),
938
939 /// The operation was partially successful but there were problems.
940 PartialFailure(T, E),
941
942 /// The operation was entirely unsuccessful.
943 TotalFailure(E),
944}
945
946impl<T, E> Outcome<T, E>
947where
948 E: Debug,
949{
950 #[cfg(test)]
951 /// Panic if there's any errors
952 pub fn unwrap(self) -> T {
953 match self {
954 Outcome::Ok(t) => t,
955 Outcome::PartialFailure(_, errors) => panic!("Error: {:?}", errors),
956 Outcome::TotalFailure(error) => panic!("Error: {:?}", error),
957 }
958 }
959
960 /// Panic if there's any errors
961 pub fn expect(self, e: &'static str) -> T {
962 match self {
963 Outcome::Ok(t) => t,
964 Outcome::PartialFailure(_, errors) => panic!("{e}: {:?}", errors),
965 Outcome::TotalFailure(error) => panic!("{e}: {:?}", error),
966 }
967 }
968
969 pub fn into_result(self) -> Result<T, E> {
970 match self {
971 Outcome::Ok(t) => Ok(t),
972 Outcome::PartialFailure(_, e) | Outcome::TotalFailure(e) => Err(e),
973 }
974 }
975
976 pub fn map<T2>(self, f: impl FnOnce(T) -> T2) -> Outcome<T2, E> {
977 match self {
978 Outcome::Ok(t) => Outcome::Ok(f(t)),
979 Outcome::PartialFailure(t, e) => Outcome::PartialFailure(f(t), e),
980 Outcome::TotalFailure(e) => Outcome::TotalFailure(e),
981 }
982 }
983}