Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / build.rs
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}