From 370da15569040dfcdf71d1c6b975b5fe5ae5e66f Mon Sep 17 00:00:00 2001 From: Gears Date: Wed, 19 Nov 2025 17:49:31 +0000 Subject: [PATCH] Always generate type definitions for private types --- CHANGELOG.md | 4 ++ compiler-core/src/erlang.rs | 7 --- .../src/erlang/tests/custom_types.rs | 16 ++++++ ...ue_constructor_is_generated_correctly.snap | 24 +++++++++ ...ests__records__private_unused_records.snap | 6 ++- .../src/javascript/tests/custom_types.rs | 16 ++++++ ...ue_constructor_is_generated_correctly.snap | 30 +++++++++++ ...pe_alias__private_type_in_opaque_type.snap | 4 ++ compiler-core/src/javascript/typescript.rs | 54 ++++++++----------- ...d_tests__import_shadowed_name_warning.snap | 3 ++ 10 files changed, 124 insertions(+), 40 deletions(-) create mode 100644 compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 638b38b925b..ab7131dd875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -265,3 +265,7 @@ - Typos in the error message shown when trying to install a non-existent package have been fixed. ([Ioan Clarke](https://github.com/ioanclarke)) + +- Fixed a bug where the compiler would generate invalid Erlang and TypeScript + code for unused opaque types referencing private types. + ([Surya Rose](https://github.com/GearsDatapacks)) diff --git a/compiler-core/src/erlang.rs b/compiler-core/src/erlang.rs index c7d2e73bda7..39ad645a1dd 100644 --- a/compiler-core/src/erlang.rs +++ b/compiler-core/src/erlang.rs @@ -181,7 +181,6 @@ fn module_document<'a>( &mut type_defs, &module.name, &overridden_publicity, - &module.unused_definition_positions, ); } @@ -303,13 +302,7 @@ fn register_imports_and_exports( type_defs: &mut Vec>, module_name: &str, overridden_publicity: &im::HashSet, - unused_definition_positions: &HashSet, ) { - // Do not generate any code for unused items - if unused_definition_positions.contains(&definition.location().start) { - return; - } - match definition { Definition::Function(Function { publicity, diff --git a/compiler-core/src/erlang/tests/custom_types.rs b/compiler-core/src/erlang/tests/custom_types.rs index 92c0a4d5c54..42fafba2817 100644 --- a/compiler-core/src/erlang/tests/custom_types.rs +++ b/compiler-core/src/erlang/tests/custom_types.rs @@ -27,3 +27,19 @@ pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) "# ); } + +// https://github.com/gleam-lang/gleam/issues/5127 +#[test] +fn unused_opaque_constructor_is_generated_correctly() { + assert_erl!( + " +type Wibble { + Wibble +} + +pub opaque type Wobble { + Wobble(Wibble) +} +" + ); +} diff --git a/compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap b/compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap new file mode 100644 index 00000000000..1220d9a63e4 --- /dev/null +++ b/compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap @@ -0,0 +1,24 @@ +--- +source: compiler-core/src/erlang/tests/custom_types.rs +expression: "\ntype Wibble {\n Wibble\n}\n\npub opaque type Wobble {\n Wobble(Wibble)\n}\n" +--- +----- SOURCE CODE + +type Wibble { + Wibble +} + +pub opaque type Wobble { + Wobble(Wibble) +} + + +----- COMPILED ERLANG +-module(my@mod). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "project/test/my/mod.gleam"). +-export_type([wibble/0, wobble/0]). + +-type wibble() :: wibble. + +-opaque wobble() :: {wobble, wibble()}. diff --git a/compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__private_unused_records.snap b/compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__private_unused_records.snap index 441abd3fd94..0b39afb239a 100644 --- a/compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__private_unused_records.snap +++ b/compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__private_unused_records.snap @@ -18,10 +18,14 @@ pub fn main(x: Int) -> Int { -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). --export_type([a/0]). +-export_type([a/0, b/0, c/0]). -type a() :: {a, integer()}. +-type b() :: {b, binary()}. + +-type c() :: {c, integer()}. + -file("project/test/my/mod.gleam", 5). -spec main(integer()) -> integer(). main(X) -> diff --git a/compiler-core/src/javascript/tests/custom_types.rs b/compiler-core/src/javascript/tests/custom_types.rs index 3c1ed30a170..c65da5b38d4 100644 --- a/compiler-core/src/javascript/tests/custom_types.rs +++ b/compiler-core/src/javascript/tests/custom_types.rs @@ -960,3 +960,19 @@ pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) "# ); } + +// https://github.com/gleam-lang/gleam/issues/5127 +#[test] +fn unused_opaque_constructor_is_generated_correctly() { + assert_ts_def!( + " +type Wibble { + Wibble +} + +pub opaque type Wobble { + Wobble(Wibble) +} +" + ); +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap new file mode 100644 index 00000000000..f3a54e64c1d --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap @@ -0,0 +1,30 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\ntype Wibble {\n Wibble\n}\n\npub opaque type Wobble {\n Wobble(Wibble)\n}\n" +--- +----- SOURCE CODE + +type Wibble { + Wibble +} + +pub opaque type Wobble { + Wobble(Wibble) +} + + +----- TYPESCRIPT DEFINITIONS +import type * as _ from "../gleam.d.mts"; + +declare class Wibble extends _.CustomType {} + +type Wibble$ = Wibble; + +declare class Wobble extends _.CustomType { + /** @deprecated */ + constructor(argument$0: Wibble$); + /** @deprecated */ + 0: Wibble$; +} + +export type Wobble$ = Wobble; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__type_alias__private_type_in_opaque_type.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__type_alias__private_type_in_opaque_type.snap index 2b487e32587..3908e877b15 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__type_alias__private_type_in_opaque_type.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__type_alias__private_type_in_opaque_type.snap @@ -16,6 +16,10 @@ pub opaque type OpaqueType { ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; +declare class PrivateType extends _.CustomType {} + +type PrivateType$ = PrivateType; + declare class OpaqueType extends _.CustomType { /** @deprecated */ constructor(argument$0: PrivateType$); diff --git a/compiler-core/src/javascript/typescript.rs b/compiler-core/src/javascript/typescript.rs index 1c4f1b77b0c..0ea7c324994 100644 --- a/compiler-core/src/javascript/typescript.rs +++ b/compiler-core/src/javascript/typescript.rs @@ -11,7 +11,7 @@ //! //! -use crate::ast::{AssignName, Publicity, SrcSpan}; +use crate::ast::{AssignName, Publicity, TypedCustomType}; use crate::javascript::import::Member; use crate::type_::{PRELUDE_MODULE_NAME, RecordAccessor, is_prelude_module}; use crate::{ @@ -380,23 +380,7 @@ impl<'a> TypeScriptGenerator<'a> { Definition::Import(Import { .. }) => vec![], - Definition::CustomType(CustomType { - publicity, - constructors, - opaque, - name, - typed_parameters, - external_javascript, - .. - }) if publicity.is_importable() => self.custom_type_definition( - name, - typed_parameters, - constructors, - *opaque, - external_javascript, - imports, - ), - Definition::CustomType(CustomType { .. }) => vec![], + Definition::CustomType(type_) => self.custom_type_definition(type_, imports), Definition::ModuleConstant(ModuleConstant { publicity, @@ -439,15 +423,21 @@ impl<'a> TypeScriptGenerator<'a> { /// fn custom_type_definition( &mut self, - name: &'a str, - typed_parameters: &'a [Arc], - constructors: &'a [TypedRecordConstructor], - opaque: bool, - external: &'a Option<(EcoString, EcoString, SrcSpan)>, + type_: &'a TypedCustomType, imports: &mut Imports<'_>, ) -> Vec> { + let CustomType { + publicity, + constructors, + opaque, + name, + typed_parameters, + external_javascript, + .. + } = type_; + // Constructors for opaque and private types are not exported - let constructor_publicity = if opaque { + let constructor_publicity = if publicity.is_private() || *opaque { Publicity::Private } else { Publicity::Public @@ -469,7 +459,7 @@ impl<'a> TypeScriptGenerator<'a> { .collect_vec(); let definition = if constructors.is_empty() { - if let Some((module, external_name, _location)) = external { + if let Some((module, external_name, _location)) = external_javascript { let member = Member { name: external_name.to_doc(), alias: Some(eco_format!("{name}$").to_doc()), @@ -491,13 +481,13 @@ impl<'a> TypeScriptGenerator<'a> { join(constructors, break_("| ", " | ")) }; - definitions.push(docvec![ - "export type ", - type_name.clone(), - " = ", - definition, - ";", - ]); + let head = if publicity.is_private() { + "type " + } else { + "export type " + }; + + definitions.push(docvec![head, type_name.clone(), " = ", definition, ";",]); // Generate getters for fields shared between variants if let Some(accessors_map) = self.module.type_info.accessors.get(name) diff --git a/test-package-compiler/src/snapshots/test_package_compiler__generated_tests__import_shadowed_name_warning.snap b/test-package-compiler/src/snapshots/test_package_compiler__generated_tests__import_shadowed_name_warning.snap index e0f6b0fb4b7..90bea32fa12 100644 --- a/test-package-compiler/src/snapshots/test_package_compiler__generated_tests__import_shadowed_name_warning.snap +++ b/test-package-compiler/src/snapshots/test_package_compiler__generated_tests__import_shadowed_name_warning.snap @@ -36,6 +36,7 @@ expression: "./cases/import_shadowed_name_warning" -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([use_type/1]). +-export_type([shadowing/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). @@ -47,6 +48,8 @@ expression: "./cases/import_shadowed_name_warning" ?MODULEDOC(" https://github.com/gleam-lang/otp/pull/22\n"). +-type shadowing() :: port. + -file("src/two.gleam", 14). -spec use_type(one:port_()) -> nil. use_type(Port) ->