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

Configure Feed

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

Generate typescript to reexport externally annotated types

author
Gears
committer
Louis Pilfold
date (Oct 27, 2025, 4:31 PM UTC) commit 741b9135 parent 31da0dae change-id rrwytztq
+87 -15
+1 -1
compiler-core/src/javascript/import.rs
··· 66 66 .group(); 67 67 imports 68 68 .append(line()) 69 - .append("export {") 69 + .append("export type {") 70 70 .append(names) 71 71 .append("};") 72 72 .append(line())
+23
compiler-core/src/javascript/tests/custom_types.rs
··· 687 687 "# 688 688 ); 689 689 } 690 + 691 + #[test] 692 + fn external_annotation() { 693 + assert_ts_def!( 694 + r#" 695 + @external(javascript, "./gleam_stdlib.d.ts", "Dict") 696 + pub type Dict(key, value) 697 + "# 698 + ); 699 + } 700 + 701 + #[test] 702 + fn external_annotated_type_used_in_function() { 703 + assert_ts_def!( 704 + r#" 705 + @external(javascript, "./gleam_stdlib.d.ts", "Dict") 706 + pub type Dict(key, value) 707 + 708 + @external(javascript, "./gleam_stdlib.mjs", "get") 709 + pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) 710 + "# 711 + ); 712 + }
+20
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__external_annotated_type_used_in_function.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/custom_types.rs 3 + expression: "\n@external(javascript, \"./gleam_stdlib.d.ts\", \"Dict\")\npub type Dict(key, value)\n\n@external(javascript, \"./gleam_stdlib.mjs\", \"get\")\npub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil)\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + @external(javascript, "./gleam_stdlib.d.ts", "Dict") 8 + pub type Dict(key, value) 9 + 10 + @external(javascript, "./gleam_stdlib.mjs", "get") 11 + pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) 12 + 13 + 14 + ----- TYPESCRIPT DEFINITIONS 15 + import type * as _ from "../gleam.d.mts"; 16 + import type { Dict as Dict$ } from "./gleam_stdlib.d.ts"; 17 + 18 + export type { Dict$ }; 19 + 20 + export function get<K, L>(dict: Dict$<K, L>, key: K): _.Result<L, undefined>;
+14
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__external_annotation.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/custom_types.rs 3 + expression: "\n@external(javascript, \"./gleam_stdlib.d.ts\", \"Dict\")\npub type Dict(key, value)\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + @external(javascript, "./gleam_stdlib.d.ts", "Dict") 8 + pub type Dict(key, value) 9 + 10 + 11 + ----- TYPESCRIPT DEFINITIONS 12 + import type { Dict as Dict$ } from "./gleam_stdlib.d.ts"; 13 + 14 + export type { Dict$ };
+29 -14
compiler-core/src/javascript/typescript.rs
··· 11 11 //! <https://www.typescriptlang.org/> 12 12 //! <https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html> 13 13 14 - use crate::ast::{AssignName, Publicity}; 14 + use crate::ast::{AssignName, Publicity, SrcSpan}; 15 + use crate::javascript::import::Member; 15 16 use crate::type_::{PRELUDE_MODULE_NAME, RecordAccessor, is_prelude_module}; 16 17 use crate::{ 17 18 ast::{ ··· 197 198 .module 198 199 .definitions 199 200 .iter() 200 - .flat_map(|definition| self.definition(definition)); 201 + .flat_map(|definition| self.definition(definition, &mut imports)); 201 202 202 203 // Two lines between each statement 203 204 let mut statements = Itertools::intersperse(statements, lines(2)).collect_vec(); ··· 363 364 } 364 365 } 365 366 366 - fn definition(&mut self, definition: &'a TypedDefinition) -> Vec<Document<'a>> { 367 + fn definition( 368 + &mut self, 369 + definition: &'a TypedDefinition, 370 + imports: &mut Imports<'_>, 371 + ) -> Vec<Document<'a>> { 367 372 match definition { 368 373 Definition::TypeAlias(TypeAlias { 369 374 alias, ··· 381 386 opaque, 382 387 name, 383 388 typed_parameters, 389 + external_javascript, 384 390 .. 385 - }) => self.custom_type_definition( 391 + }) if publicity.is_importable() => self.custom_type_definition( 386 392 name, 387 393 typed_parameters, 388 394 constructors, 389 395 *opaque, 390 - publicity, 396 + external_javascript, 397 + imports, 391 398 ), 399 + Definition::CustomType(CustomType { .. }) => vec![], 392 400 393 401 Definition::ModuleConstant(ModuleConstant { 394 402 publicity, ··· 435 443 typed_parameters: &'a [Arc<Type>], 436 444 constructors: &'a [TypedRecordConstructor], 437 445 opaque: bool, 438 - publicity: &Publicity, 446 + external: &'a Option<(EcoString, EcoString, SrcSpan)>, 447 + imports: &mut Imports<'_>, 439 448 ) -> Vec<Document<'a>> { 440 449 // Constructors for opaque and private types are not exported 441 - let constructor_publicity = if opaque || !publicity.is_importable() { 450 + let constructor_publicity = if opaque { 442 451 Publicity::Private 443 452 } else { 444 453 Publicity::Public ··· 460 469 .collect_vec(); 461 470 462 471 let definition = if constructors.is_empty() { 463 - "any".to_doc() 472 + if let Some((module, external_name, _location)) = external { 473 + let member = Member { 474 + name: external_name.to_doc(), 475 + alias: Some(eco_format!("{name}$").to_doc()), 476 + }; 477 + imports.register_export(eco_format!("{name}$")); 478 + 479 + imports.register_module(module.clone(), [], [member]); 480 + return Vec::new(); 481 + } else { 482 + "any".to_doc() 483 + } 464 484 } else { 465 485 let constructors = constructors.iter().map(|x| { 466 486 name_with_generics( ··· 472 492 }; 473 493 474 494 definitions.push(docvec![ 475 - if publicity.is_importable() { 476 - "export ".to_doc() 477 - } else { 478 - "declare ".to_doc() 479 - }, 480 - "type ", 495 + "export type ", 481 496 type_name.clone(), 482 497 " = ", 483 498 definition,