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 676 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 pub 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 398 .types 399 .iter() 400 .filter(|(_, c)| c.publicity.is_public()) 401 { 402 let mut id_map = IdMap::new(); 403 404 let TypeConstructor { 405 deprecation, 406 documentation, 407 .. 408 } = constructor; 409 410 for typed_parameter in &constructor.parameters { 411 id_map.add_type_variable_id(typed_parameter.as_ref()); 412 } 413 414 let _ = types.insert( 415 name.clone(), 416 TypeDefinitionInterface { 417 documentation: documentation.clone(), 418 deprecation: DeprecationInterface::from_deprecation(&deprecation), 419 parameters: interface 420 .types 421 .get(&name.clone()) 422 .map_or(vec![], |t| t.parameters.clone()) 423 .len(), 424 constructors: if constructor.opaque { 425 vec![] 426 } else { 427 match interface.types_value_constructors.get(&name.clone()) { 428 Some(constructors) => constructors 429 .variants 430 .iter() 431 .map(|constructor| TypeConstructorInterface { 432 // TODO: Find documentation 433 documentation: None, 434 name: constructor.name.clone(), 435 parameters: constructor 436 .parameters 437 .iter() 438 .map(|arg| ParameterInterface { 439 label: arg.label.clone(), 440 // We share the same id_map between each step so that the 441 // incremental ids assigned are consisten with each other 442 type_: from_type_helper( 443 arg.type_.as_ref(), 444 &mut id_map, 445 ), 446 }) 447 .collect(), 448 }) 449 .collect(), 450 None => vec![], 451 } 452 }, 453 }, 454 ); 455 } 456 457 for (name, alias) in interface 458 .type_aliases 459 .iter() 460 .filter(|(_, v)| v.publicity.is_public()) 461 { 462 let _ = type_aliases.insert( 463 name.clone(), 464 TypeAliasInterface { 465 documentation: alias.documentation.clone(), 466 deprecation: DeprecationInterface::from_deprecation(&alias.deprecation), 467 parameters: alias.arity, 468 alias: TypeInterface::from_type(&alias.type_), 469 }, 470 ); 471 } 472 473 for (name, value) in interface 474 .values 475 .iter() 476 .filter(|(_, v)| v.publicity.is_public()) 477 { 478 match (value.type_.as_ref(), value.variant.clone()) { 479 ( 480 Type::Fn { 481 args: arguments, 482 retrn: return_type, 483 }, 484 ValueConstructorVariant::ModuleFn { 485 documentation, 486 implementations, 487 .. 488 }, 489 ) => { 490 let mut id_map = IdMap::new(); 491 492 let _ = functions.insert( 493 name.clone(), 494 FunctionInterface { 495 implementations: ImplementationsInterface::from_implementations( 496 &implementations, 497 ), 498 deprecation: DeprecationInterface::from_deprecation(&value.deprecation), 499 documentation, 500 parameters: arguments 501 .iter() 502 .map(|arg| ParameterInterface { 503 // TODO: fixme 504 label: None, 505 type_: from_type_helper(arg, &mut id_map), 506 }) 507 .collect(), 508 return_: from_type_helper(&return_type, &mut id_map), 509 }, 510 ); 511 } 512 513 ( 514 type_, 515 ValueConstructorVariant::ModuleConstant { 516 documentation, 517 implementations, 518 .. 519 }, 520 ) => { 521 let _ = constants.insert( 522 name.clone(), 523 ConstantInterface { 524 implementations: ImplementationsInterface::from_implementations( 525 &implementations, 526 ), 527 type_: TypeInterface::from_type(type_), 528 deprecation: DeprecationInterface::from_deprecation(&value.deprecation), 529 documentation, 530 }, 531 ); 532 } 533 534 _ => {} 535 } 536 } 537 538 ModuleInterface { 539 documentation: interface.documentation.clone(), 540 types, 541 type_aliases, 542 constants, 543 functions, 544 } 545 } 546} 547 548impl TypeInterface { 549 fn from_type(type_: &Type) -> TypeInterface { 550 from_type_helper(type_, &mut IdMap::new()) 551 } 552} 553 554/// Turns a type into its interface, an `IdMap` is needed to make sure that all 555/// the type variables' ids that appear in the type are mapped to an incremental 556/// number and consistent with each other (that is, two types variables that 557/// have the same id will also have the same incremental number in the end). 558fn from_type_helper(type_: &Type, id_map: &mut IdMap) -> TypeInterface { 559 match type_ { 560 Type::Fn { args, retrn } => TypeInterface::Fn { 561 parameters: args 562 .iter() 563 .map(|arg| from_type_helper(arg.as_ref(), id_map)) 564 .collect(), 565 return_: Box::new(from_type_helper(retrn, id_map)), 566 }, 567 568 Type::Tuple { elems } => TypeInterface::Tuple { 569 elements: elems 570 .iter() 571 .map(|elem| from_type_helper(elem.as_ref(), id_map)) 572 .collect(), 573 }, 574 575 Type::Var { type_ } => match type_ 576 .as_ref() 577 .try_borrow() 578 .expect("borrow type after inference") 579 .deref() 580 { 581 TypeVar::Link { type_ } => from_type_helper(type_, id_map), 582 // Since package serialisation happens after inference there 583 // should be no unbound type variables. 584 // TODO: This branch should be `unreachable!()` but because of 585 // https://github.com/gleam-lang/gleam/issues/2533 586 // we sometimes end up with those in top level 587 // definitions. 588 // However, `Unbound` and `Generic` ids are generated 589 // using the same generator so we have no problem treating 590 // unbound variables as generic ones since ids will never 591 // overlap. 592 // Once #2533 is closed this branch can be turned back to 593 // be unreachable!(). 594 TypeVar::Unbound { id } | TypeVar::Generic { id } => TypeInterface::Variable { 595 id: id_map.map_id(*id), 596 }, 597 }, 598 599 Type::Named { 600 name, 601 module, 602 args, 603 package, 604 .. 605 } => TypeInterface::Named { 606 name: name.clone(), 607 package: package.clone(), 608 module: module.clone(), 609 parameters: args 610 .iter() 611 .map(|arg| from_type_helper(arg.as_ref(), id_map)) 612 .collect(), 613 }, 614 } 615} 616 617/// This is a map that is used to map type variable id's to progressive numbers 618/// starting from 0. 619/// After type inference the ids associated with type variables can be quite 620/// high and are not the best to produce a human/machine readable output. 621/// 622/// Imagine a function like this one: `pub fn wibble(item: a, rest: b) -> c` 623/// What we want here is for type variables to have increasing ids starting from 624/// 0: `a` with id `0`, `b` with id `1` and `c` with id `2`. 625/// 626/// This map allows us to keep track of the ids we've run into and map those to 627/// their incremental counterpart starting from 0. 628struct IdMap { 629 next_id: u64, 630 ids: HashMap<u64, u64>, 631} 632 633impl IdMap { 634 /// Create a new map that will assign id numbers starting from 0. 635 fn new() -> IdMap { 636 IdMap { 637 next_id: 0, 638 ids: HashMap::new(), 639 } 640 } 641 642 /// Map an id to its mapped counterpart starting from 0. If an id has never 643 /// been seen before it will be assigned a new incremental number. 644 fn map_id(&mut self, id: u64) -> u64 { 645 match self.ids.get(&id) { 646 Some(mapped_id) => *mapped_id, 647 None => { 648 let mapped_id = self.next_id; 649 let _ = self.ids.insert(id, mapped_id); 650 self.next_id += 1; 651 mapped_id 652 } 653 } 654 } 655 656 /// If the type is a type variable, and has not been seen before, it will 657 /// be assigned to a new incremental number. 658 fn add_type_variable_id(&mut self, type_: &Type) { 659 match type_ { 660 // These types have no id to add to the map. 661 Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => (), 662 // If the type is actually a type variable whose id needs to be mapped. 663 Type::Var { type_ } => match type_ 664 .as_ref() 665 .try_borrow() 666 .expect("borrow type after inference") 667 .deref() 668 { 669 TypeVar::Link { .. } => (), 670 TypeVar::Unbound { id } | TypeVar::Generic { id } => { 671 let _ = self.map_id(*id); 672 } 673 }, 674 } 675 } 676}