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 / package_interface.rs
25 kB 675 lines
1use std::{collections::HashMap, ops::Deref}; 2 3use ecow::EcoString; 4use serde::Serialize; 5 6#[cfg(test)] 7mod tests; 8 9use crate::{ 10 io::ordered_map, 11 type_::{ 12 self, Deprecation, Type, TypeConstructor, TypeVar, ValueConstructorVariant, 13 expression::Implementations, 14 }, 15}; 16 17use crate::build::Package; 18 19/// The public interface of a package that gets serialised as a json object. 20#[derive(Serialize, Debug)] 21#[serde(rename_all = "kebab-case")] 22pub struct PackageInterface { 23 name: EcoString, 24 version: EcoString, 25 /// The Gleam version constraint that the package specifies in its `gleam.toml`. 26 gleam_version_constraint: Option<EcoString>, 27 /// A map from module name to its interface. 28 #[serde(serialize_with = "ordered_map")] 29 modules: HashMap<EcoString, ModuleInterface>, 30} 31 32#[derive(Serialize, Debug)] 33#[serde(rename_all = "kebab-case")] 34pub struct ModuleInterface { 35 /// A vector with the lines composing the module's documentation (that is 36 /// every line preceded by a `////`). 37 documentation: Vec<EcoString>, 38 /// A map from type alias name to its interface. 39 #[serde(serialize_with = "ordered_map")] 40 type_aliases: HashMap<EcoString, TypeAliasInterface>, 41 /// A map from type name to its interface. 42 #[serde(serialize_with = "ordered_map")] 43 types: HashMap<EcoString, TypeDefinitionInterface>, 44 /// A map from constant name to its interface. 45 #[serde(serialize_with = "ordered_map")] 46 constants: HashMap<EcoString, ConstantInterface>, 47 /// A map from function name to its interface. 48 #[serde(serialize_with = "ordered_map")] 49 functions: HashMap<EcoString, FunctionInterface>, 50} 51 52#[derive(Serialize, Debug)] 53#[serde(rename_all = "kebab-case")] 54pub struct TypeDefinitionInterface { 55 /// The definition's documentation comment (that is every line preceded by 56 /// `///`). 57 documentation: Option<EcoString>, 58 /// If the definition has a deprecation annotation `@deprecated("...")` 59 /// this field will hold the reason of the deprecation. 60 deprecation: Option<DeprecationInterface>, 61 /// The number of type variables in the type definition. 62 /// ```gleam 63 /// /// This type has 2 type variables. 64 /// type Result(a, b) { 65 /// Ok(a) 66 /// Error(b) 67 /// } 68 /// ``` 69 parameters: usize, 70 /// A list of the type constructors. If the type is marked as opaque it 71 /// won't have any visible constructors. 72 constructors: Vec<TypeConstructorInterface>, 73} 74 75#[derive(Serialize, Debug)] 76#[serde(rename_all = "kebab-case")] 77pub struct TypeConstructorInterface { 78 /// The constructor's documentation comment (that is every line preceded by 79 /// `///`). 80 documentation: Option<EcoString>, 81 /// The name of the type constructor. 82 /// ```gleam 83 /// pub type Box(a) { 84 /// MyBox(value: a) 85 /// //^^^^^ This is the constructor's name 86 /// } 87 /// ``` 88 name: EcoString, 89 /// A list of the parameters needed by the constructor. 90 /// ```gleam 91 /// pub type Box(a) { 92 /// MyBox(value: a) 93 /// // ^^^^^^^^ This is the constructor's parameter. 94 /// } 95 /// ``` 96 parameters: Vec<ParameterInterface>, 97} 98 99#[derive(Serialize, Debug)] 100#[serde(rename_all = "kebab-case")] 101pub struct TypeAliasInterface { 102 /// The constructor's documentation comment (that is every line preceded by 103 /// `///`). 104 documentation: Option<EcoString>, 105 /// If the alias has a deprecation annotation `@deprecated("...")` 106 /// this field will hold the reason of the deprecation. 107 deprecation: Option<DeprecationInterface>, 108 /// The number of type variables in the type alias definition. 109 /// ```gleam 110 /// /// This type alias has 2 type variables. 111 /// type Results(a, b) = List(Restul(a, b)) 112 /// ``` 113 parameters: usize, 114 /// The aliased type. 115 /// ```gleam 116 /// type Ints = List(Int) 117 /// // ^^^^^^^^^ This is the aliased type in a type alias. 118 /// ``` 119 alias: TypeInterface, 120} 121 122#[derive(Serialize, Debug)] 123#[serde(rename_all = "kebab-case")] 124pub struct ConstantInterface { 125 /// The constant's documentation comment (that is every line preceded by 126 /// `///`). 127 documentation: Option<EcoString>, 128 /// If the constant has a deprecation annotation `@deprecated("...")` 129 /// this field will hold the reason of the deprecation. 130 deprecation: Option<DeprecationInterface>, 131 implementations: ImplementationsInterface, 132 /// The constant's type. 133 #[serde(rename = "type")] 134 type_: TypeInterface, 135} 136 137/// A module's function. This differs from a simple `Fn` type as its arguments 138/// can be labelled. 139#[derive(Serialize, Debug)] 140#[serde(rename_all = "kebab-case")] 141pub struct FunctionInterface { 142 /// The function's documentation comment (that is every line preceded by 143 /// `///`). 144 documentation: Option<EcoString>, 145 /// If the constant has a deprecation annotation `@deprecated("...")` 146 /// this field will hold the reason of the deprecation. 147 deprecation: Option<DeprecationInterface>, 148 implementations: ImplementationsInterface, 149 parameters: Vec<ParameterInterface>, 150 #[serde(rename = "return")] 151 return_: TypeInterface, 152} 153 154/// Informations about how a value is implemented. 155#[derive(Debug, Serialize, Copy, Clone)] 156#[serde(rename_all = "kebab-case")] 157pub struct ImplementationsInterface { 158 /// Set to `true` if the const/function has a pure Gleam implementation 159 /// (that is, it never uses external code). 160 /// Being pure Gleam means that the function will support all Gleam 161 /// targets, even future ones that are not present to this day. 162 /// 163 /// Consider the following function: 164 /// 165 /// ```gleam 166 /// @external(erlang, "wibble", "wobble") 167 /// pub fn a_random_number() -> Int { 168 /// 4 169 /// // This is a default implementation. 170 /// } 171 /// ``` 172 /// 173 /// The implementations for this function will look like this: 174 /// 175 /// ```json 176 /// { 177 /// gleam: true, 178 /// can_run_on_erlang: true, 179 /// can_run_on_javascript: true, 180 /// uses_erlang_externals: true, 181 /// uses_javascript_externals: false, 182 /// } 183 /// ``` 184 /// 185 /// - `gleam: true` means that the function has a pure Gleam implementation 186 /// and thus it can be used on all Gleam targets with no problems. 187 /// - `can_run_on_erlang: false` the function can be called on the Erlang 188 /// target. 189 /// - `can_run_on_javascript: true` the function can be called on the JavaScript 190 /// target. 191 /// - `uses_erlang_externals: true` means that the function will use Erlang 192 /// external code when compiled to the Erlang target. 193 /// - `uses_javascript_externals: false` means that the function won't use 194 /// JavaScript external code when compiled to JavaScript. The function can 195 /// still be used on the JavaScript target since it has a pure Gleam 196 /// implementation. 197 gleam: bool, 198 /// Set to `true` if the const/function is defined using Erlang external 199 /// code. That means that the function will use Erlang code through FFI when 200 /// compiled for the Erlang target. 201 uses_erlang_externals: bool, 202 /// Set to `true` if the const/function is defined using JavaScript external 203 /// code. That means that the function will use JavaScript code through FFI 204 /// when compiled for the JavaScript target. 205 /// 206 /// Let's have a look at an example: 207 /// 208 /// ```gleam 209 /// @external(javascript, "wibble", "wobble") 210 /// pub fn javascript_only() -> Int 211 /// ``` 212 /// 213 /// It's implementations field will look like this: 214 /// 215 /// ```json 216 /// { 217 /// gleam: false, 218 /// can_run_on_erlang: false, 219 /// can_run_on_javascript: true, 220 /// uses_erlang_externals: false, 221 /// uses_javascript_externals: true, 222 /// } 223 /// ``` 224 /// 225 /// - `gleam: false` means that the function doesn't have a pure Gleam 226 /// implementations. This means that the function is only defined using 227 /// externals and can only be used on some targets. 228 /// - `can_run_on_erlang: false` the function cannot be called on the Erlang 229 /// target. 230 /// - `can_run_on_javascript: true` the function can be called on the JavaScript 231 /// target. 232 /// - `uses_erlang_externals: false` the function is not using external 233 /// Erlang code. 234 /// - `uses_javascript_externals: true` the function is using JavaScript 235 /// external code. 236 uses_javascript_externals: bool, 237 /// Whether the function can be called on the Erlang target, either due to a 238 /// pure Gleam implementation or an implementation that uses some Erlang 239 /// externals. 240 can_run_on_erlang: bool, 241 /// Whether the function can be called on the JavaScript target, either due 242 /// to a pure Gleam implementation or an implementation that uses some 243 /// JavaScript externals. 244 can_run_on_javascript: bool, 245} 246 247impl ImplementationsInterface { 248 fn from_implementations(implementations: &Implementations) -> ImplementationsInterface { 249 // It might look a bit silly to just recreate an identical structure with 250 // a different name. However, this way we won't inadvertently cause breaking 251 // changes if we were to change the names used by the `Implementations` struct 252 // that is used by the target tracking algorithm. 253 // By doing this we can change the target tracking and package interface 254 // separately! 255 // 256 // This pattern matching makes sure we will remember to handle any change 257 // in the `Implementations` struct. 258 let Implementations { 259 gleam, 260 uses_erlang_externals, 261 uses_javascript_externals, 262 263 can_run_on_erlang, 264 can_run_on_javascript, 265 } = implementations; 266 267 ImplementationsInterface { 268 gleam: *gleam, 269 uses_erlang_externals: *uses_erlang_externals, 270 uses_javascript_externals: *uses_javascript_externals, 271 can_run_on_erlang: *can_run_on_erlang, 272 can_run_on_javascript: *can_run_on_javascript, 273 } 274 } 275} 276 277#[derive(Serialize, Debug)] 278#[serde(rename_all = "kebab-case")] 279pub struct DeprecationInterface { 280 /// The reason for the deprecation. 281 message: EcoString, 282} 283 284impl DeprecationInterface { 285 fn from_deprecation(deprecation: &Deprecation) -> Option<DeprecationInterface> { 286 match deprecation { 287 Deprecation::NotDeprecated => None, 288 Deprecation::Deprecated { message } => Some(DeprecationInterface { 289 message: message.clone(), 290 }), 291 } 292 } 293} 294 295#[derive(Serialize, Debug)] 296#[serde(tag = "kind")] 297#[serde(rename_all = "kebab-case")] 298pub enum TypeInterface { 299 /// A tuple type like `#(Int, Float)`. 300 Tuple { 301 /// The types composing the tuple. 302 elements: Vec<TypeInterface>, 303 }, 304 /// A function type like `fn(Int, String) -> String`. 305 Fn { 306 parameters: Vec<TypeInterface>, 307 #[serde(rename = "return")] 308 return_: Box<TypeInterface>, 309 }, 310 /// A type variable. 311 /// ```gleam 312 /// pub fn wibble(value: a) -> a {} 313 /// // ^ This is a type variable. 314 /// ``` 315 Variable { id: u64 }, 316 /// A custom named type. 317 /// ```gleam 318 /// let value: Bool = True 319 /// ^^^^ This is a named type. 320 /// ``` 321 /// 322 /// All prelude types - like Bool, String, etc. - are named types as well. 323 /// In that case their package is an empty string `""` and their module 324 /// name is the string `"gleam"`. 325 /// 326 Named { 327 name: EcoString, 328 /// The package the type is defined in. 329 package: EcoString, 330 /// The module the type is defined in. 331 module: EcoString, 332 /// The type parameters that might be needed to define a named type. 333 /// ```gleam 334 /// let result: Result(Int, e) = Ok(1) 335 /// // ^^^^^^ The `Result` named type has 2 parameters. 336 /// // In this case it's the Int type and a type 337 /// // variable. 338 /// ``` 339 parameters: Vec<TypeInterface>, 340 }, 341} 342 343#[derive(Serialize, Debug)] 344#[serde(rename_all = "kebab-case")] 345pub struct ParameterInterface { 346 /// If the parameter is labelled this will hold the label's name. 347 /// ```gleam 348 /// pub fn repeat(times n: Int) -> List(Int) 349 /// // ^^^^^ This is the parameter's label. 350 /// ``` 351 label: Option<EcoString>, 352 /// The parameter's type. 353 /// ```gleam 354 /// pub fn repeat(times n: Int) -> List(Int) 355 /// // ^^^ This is the parameter's type. 356 /// ``` 357 #[serde(rename = "type")] 358 type_: TypeInterface, 359} 360 361impl PackageInterface { 362 pub fn from_package( 363 package: &Package, 364 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>, 365 ) -> PackageInterface { 366 PackageInterface { 367 name: package.config.name.clone(), 368 version: package.config.version.to_string().into(), 369 gleam_version_constraint: package 370 .config 371 .gleam_version 372 .clone() 373 .map(|version| EcoString::from(version.to_string())), 374 modules: package 375 .modules 376 .iter() 377 .map(|module| &module.ast.type_info) 378 .chain( 379 package 380 .module_names 381 .iter() 382 .filter_map(|name| cached_modules.get(name)), 383 ) 384 .filter(|module| !package.config.is_internal_module(module.name.as_str())) 385 .map(|module| (module.name.clone(), ModuleInterface::from_interface(module))) 386 .collect(), 387 } 388 } 389} 390 391impl ModuleInterface { 392 fn from_interface(interface: &type_::ModuleInterface) -> ModuleInterface { 393 let mut types = HashMap::new(); 394 let mut type_aliases = HashMap::new(); 395 let mut constants = HashMap::new(); 396 let mut functions = HashMap::new(); 397 for (name, constructor) in interface.types.iter().filter(|(name, c)| { 398 // Aliases are stored separately 399 c.publicity.is_public() && !interface.type_aliases.contains_key(*name) 400 }) { 401 let mut id_map = IdMap::new(); 402 403 let TypeConstructor { 404 deprecation, 405 documentation, 406 .. 407 } = constructor; 408 409 for typed_parameter in &constructor.parameters { 410 id_map.add_type_variable_id(typed_parameter.as_ref()); 411 } 412 413 let _ = types.insert( 414 name.clone(), 415 TypeDefinitionInterface { 416 documentation: documentation.clone(), 417 deprecation: DeprecationInterface::from_deprecation(&deprecation), 418 parameters: interface 419 .types 420 .get(&name.clone()) 421 .map_or(vec![], |t| t.parameters.clone()) 422 .len(), 423 constructors: if constructor.opaque { 424 vec![] 425 } else { 426 match interface.types_value_constructors.get(&name.clone()) { 427 Some(constructors) => constructors 428 .variants 429 .iter() 430 .map(|constructor| TypeConstructorInterface { 431 // TODO: Find documentation 432 documentation: None, 433 name: constructor.name.clone(), 434 parameters: constructor 435 .parameters 436 .iter() 437 .map(|arg| ParameterInterface { 438 label: arg.label.clone(), 439 // We share the same id_map between each step so that the 440 // incremental ids assigned are consisten with each other 441 type_: from_type_helper( 442 arg.type_.as_ref(), 443 &mut id_map, 444 ), 445 }) 446 .collect(), 447 }) 448 .collect(), 449 None => vec![], 450 } 451 }, 452 }, 453 ); 454 } 455 456 for (name, alias) in interface 457 .type_aliases 458 .iter() 459 .filter(|(_, v)| v.publicity.is_public()) 460 { 461 let _ = type_aliases.insert( 462 name.clone(), 463 TypeAliasInterface { 464 documentation: alias.documentation.clone(), 465 deprecation: DeprecationInterface::from_deprecation(&alias.deprecation), 466 parameters: alias.arity, 467 alias: TypeInterface::from_type(&alias.type_), 468 }, 469 ); 470 } 471 472 for (name, value) in interface 473 .values 474 .iter() 475 .filter(|(_, v)| v.publicity.is_public()) 476 { 477 match (value.type_.as_ref(), value.variant.clone()) { 478 ( 479 Type::Fn { 480 args: arguments, 481 retrn: return_type, 482 }, 483 ValueConstructorVariant::ModuleFn { 484 documentation, 485 implementations, 486 .. 487 }, 488 ) => { 489 let mut id_map = IdMap::new(); 490 491 let _ = functions.insert( 492 name.clone(), 493 FunctionInterface { 494 implementations: ImplementationsInterface::from_implementations( 495 &implementations, 496 ), 497 deprecation: DeprecationInterface::from_deprecation(&value.deprecation), 498 documentation, 499 parameters: arguments 500 .iter() 501 .map(|arg| ParameterInterface { 502 // TODO: fixme 503 label: None, 504 type_: from_type_helper(arg, &mut id_map), 505 }) 506 .collect(), 507 return_: from_type_helper(&return_type, &mut id_map), 508 }, 509 ); 510 } 511 512 ( 513 type_, 514 ValueConstructorVariant::ModuleConstant { 515 documentation, 516 implementations, 517 .. 518 }, 519 ) => { 520 let _ = constants.insert( 521 name.clone(), 522 ConstantInterface { 523 implementations: ImplementationsInterface::from_implementations( 524 &implementations, 525 ), 526 type_: TypeInterface::from_type(type_), 527 deprecation: DeprecationInterface::from_deprecation(&value.deprecation), 528 documentation, 529 }, 530 ); 531 } 532 533 _ => {} 534 } 535 } 536 537 ModuleInterface { 538 documentation: interface.documentation.clone(), 539 types, 540 type_aliases, 541 constants, 542 functions, 543 } 544 } 545} 546 547impl TypeInterface { 548 fn from_type(type_: &Type) -> TypeInterface { 549 from_type_helper(type_, &mut IdMap::new()) 550 } 551} 552 553/// Turns a type into its interface, an `IdMap` is needed to make sure that all 554/// the type variables' ids that appear in the type are mapped to an incremental 555/// number and consistent with each other (that is, two types variables that 556/// have the same id will also have the same incremental number in the end). 557fn from_type_helper(type_: &Type, id_map: &mut IdMap) -> TypeInterface { 558 match type_ { 559 Type::Fn { args, retrn } => TypeInterface::Fn { 560 parameters: args 561 .iter() 562 .map(|arg| from_type_helper(arg.as_ref(), id_map)) 563 .collect(), 564 return_: Box::new(from_type_helper(retrn, id_map)), 565 }, 566 567 Type::Tuple { elems } => TypeInterface::Tuple { 568 elements: elems 569 .iter() 570 .map(|elem| from_type_helper(elem.as_ref(), id_map)) 571 .collect(), 572 }, 573 574 Type::Var { type_ } => match type_ 575 .as_ref() 576 .try_borrow() 577 .expect("borrow type after inference") 578 .deref() 579 { 580 TypeVar::Link { type_ } => from_type_helper(type_, id_map), 581 // Since package serialisation happens after inference there 582 // should be no unbound type variables. 583 // TODO: This branch should be `unreachable!()` but because of 584 // https://github.com/gleam-lang/gleam/issues/2533 585 // we sometimes end up with those in top level 586 // definitions. 587 // However, `Unbound` and `Generic` ids are generated 588 // using the same generator so we have no problem treating 589 // unbound variables as generic ones since ids will never 590 // overlap. 591 // Once #2533 is closed this branch can be turned back to 592 // be unreachable!(). 593 TypeVar::Unbound { id } | TypeVar::Generic { id } => TypeInterface::Variable { 594 id: id_map.map_id(*id), 595 }, 596 }, 597 598 Type::Named { 599 name, 600 module, 601 args, 602 package, 603 .. 604 } => TypeInterface::Named { 605 name: name.clone(), 606 package: package.clone(), 607 module: module.clone(), 608 parameters: args 609 .iter() 610 .map(|arg| from_type_helper(arg.as_ref(), id_map)) 611 .collect(), 612 }, 613 } 614} 615 616/// This is a map that is used to map type variable id's to progressive numbers 617/// starting from 0. 618/// After type inference the ids associated with type variables can be quite 619/// high and are not the best to produce a human/machine readable output. 620/// 621/// Imagine a function like this one: `pub fn wibble(item: a, rest: b) -> c` 622/// What we want here is for type variables to have increasing ids starting from 623/// 0: `a` with id `0`, `b` with id `1` and `c` with id `2`. 624/// 625/// This map allows us to keep track of the ids we've run into and map those to 626/// their incremental counterpart starting from 0. 627struct IdMap { 628 next_id: u64, 629 ids: HashMap<u64, u64>, 630} 631 632impl IdMap { 633 /// Create a new map that will assign id numbers starting from 0. 634 fn new() -> IdMap { 635 IdMap { 636 next_id: 0, 637 ids: HashMap::new(), 638 } 639 } 640 641 /// Map an id to its mapped counterpart starting from 0. If an id has never 642 /// been seen before it will be assigned a new incremental number. 643 fn map_id(&mut self, id: u64) -> u64 { 644 match self.ids.get(&id) { 645 Some(mapped_id) => *mapped_id, 646 None => { 647 let mapped_id = self.next_id; 648 let _ = self.ids.insert(id, mapped_id); 649 self.next_id += 1; 650 mapped_id 651 } 652 } 653 } 654 655 /// If the type is a type variable, and has not been seen before, it will 656 /// be assigned to a new incremental number. 657 fn add_type_variable_id(&mut self, type_: &Type) { 658 match type_ { 659 // These types have no id to add to the map. 660 Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => (), 661 // If the type is actually a type variable whose id needs to be mapped. 662 Type::Var { type_ } => match type_ 663 .as_ref() 664 .try_borrow() 665 .expect("borrow type after inference") 666 .deref() 667 { 668 TypeVar::Link { .. } => (), 669 TypeVar::Unbound { id } | TypeVar::Generic { id } => { 670 let _ = self.map_id(*id); 671 } 672 }, 673 } 674 } 675}