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