⭐️ A friendly language for building type-safe, scalable systems!
1

Configure Feed

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

Implement external annotations for external types

authored by

Gears and committed by
Louis Pilfold
(Oct 27, 2025, 4:31 PM UTC) 31da0dae f13da6ae

+249 -8
+13
compiler-core/src/analyse.rs
··· 921 921 parameters, 922 922 constructors, 923 923 deprecation, 924 + external_erlang, 925 + external_javascript, 924 926 .. 925 927 } = t; 926 928 ··· 1030 1032 }); 1031 1033 } 1032 1034 1035 + if external_erlang.is_some() || external_javascript.is_some() { 1036 + self.track_feature_usage(FeatureKind::ExternalCustomType, location); 1037 + 1038 + if !constructors.is_empty() { 1039 + self.problems 1040 + .error(Error::ExternalTypeWithConstructors { location }); 1041 + } 1042 + } 1043 + 1033 1044 Ok(Definition::CustomType(CustomType { 1034 1045 documentation: doc, 1035 1046 location, ··· 1042 1053 constructors, 1043 1054 typed_parameters, 1044 1055 deprecation, 1056 + external_erlang, 1057 + external_javascript, 1045 1058 })) 1046 1059 } 1047 1060
+10
compiler-core/src/ast.rs
··· 813 813 /// Once type checked this field will contain the type information for the 814 814 /// type parameters. 815 815 pub typed_parameters: Vec<T>, 816 + pub external_erlang: Option<(EcoString, EcoString, SrcSpan)>, 817 + pub external_javascript: Option<(EcoString, EcoString, SrcSpan)>, 816 818 } 817 819 818 820 impl<T> CustomType<T> { ··· 1067 1069 #[must_use] 1068 1070 pub fn is_function(&self) -> bool { 1069 1071 matches!(self, Self::Function(..)) 1072 + } 1073 + 1074 + /// Returns `true` if the module statement is [`CustomType`]. 1075 + /// 1076 + /// [`CustomType`]: ModuleStatement::CustomType 1077 + #[must_use] 1078 + pub fn is_custom_type(&self) -> bool { 1079 + matches!(self, Self::CustomType(..)) 1070 1080 } 1071 1081 1072 1082 pub fn put_doc(&mut self, new_doc: (u32, EcoString)) {
+20
compiler-core/src/error.rs
··· 4005 4005 extra_labels: vec![], 4006 4006 }), 4007 4007 }, 4008 + 4009 + TypeError::ExternalTypeWithConstructors { location } => Diagnostic { 4010 + title: "External type with constructors".to_string(), 4011 + text: wrap_format!( 4012 + "This type is annotated with the `@external` annotation, \ 4013 + but it has constructors. The `@external` annotation is only for external types \ 4014 + with no constructors." 4015 + ), 4016 + hint: Some("Remove the `@external` annotation".into()), 4017 + level: Level::Error, 4018 + location: Some(Location { 4019 + label: Label { 4020 + text: None, 4021 + span: *location, 4022 + }, 4023 + path: path.clone(), 4024 + src: src.clone(), 4025 + extra_labels: vec![], 4026 + }), 4027 + }, 4008 4028 }) 4009 4029 .collect_vec(), 4010 4030
+8 -4
compiler-core/src/parse.rs
··· 363 363 }?; 364 364 365 365 match (def, location) { 366 - (Some(definition), _) if definition.is_function() => Ok(Some(TargetedDefinition { 367 - definition, 368 - target: attributes.target, 369 - })), 366 + (Some(definition), _) if definition.is_function() || definition.is_custom_type() => { 367 + Ok(Some(TargetedDefinition { 368 + definition, 369 + target: attributes.target, 370 + })) 371 + } 370 372 371 373 (Some(definition), None) => Ok(Some(TargetedDefinition { 372 374 definition, ··· 2559 2561 constructors, 2560 2562 typed_parameters: vec![], 2561 2563 deprecation: std::mem::take(&mut attributes.deprecated), 2564 + external_erlang: std::mem::take(&mut attributes.external_erlang), 2565 + external_javascript: std::mem::take(&mut attributes.external_javascript), 2562 2566 }))) 2563 2567 } 2564 2568
+2
compiler-core/src/parse/snapshots/gleam_core__parse__tests__deprecation_attribute_on_type_variant.snap
··· 59 59 opaque: false, 60 60 parameters: [], 61 61 typed_parameters: [], 62 + external_erlang: None, 63 + external_javascript: None, 62 64 }, 63 65 ), 64 66 target: None,
+93
compiler-core/src/parse/snapshots/gleam_core__parse__tests__external_attribute_with_custom_type.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "\n@external(erlang, \"gleam_stdlib\", \"dict\")\n@external(javascript, \"./gleam_stdlib.d.ts\", \"Dict\")\npub type Dict(key, value)\n" 4 + --- 5 + Parsed { 6 + module: Module { 7 + name: "", 8 + documentation: [], 9 + type_info: (), 10 + definitions: [ 11 + TargetedDefinition { 12 + definition: CustomType( 13 + CustomType { 14 + location: SrcSpan { 15 + start: 96, 16 + end: 121, 17 + }, 18 + end_position: 121, 19 + name: "Dict", 20 + name_location: SrcSpan { 21 + start: 105, 22 + end: 109, 23 + }, 24 + publicity: Public, 25 + constructors: [], 26 + documentation: None, 27 + deprecation: NotDeprecated, 28 + opaque: false, 29 + parameters: [ 30 + ( 31 + SrcSpan { 32 + start: 110, 33 + end: 113, 34 + }, 35 + "key", 36 + ), 37 + ( 38 + SrcSpan { 39 + start: 115, 40 + end: 120, 41 + }, 42 + "value", 43 + ), 44 + ], 45 + typed_parameters: [], 46 + external_erlang: Some( 47 + ( 48 + "gleam_stdlib", 49 + "dict", 50 + SrcSpan { 51 + start: 1, 52 + end: 42, 53 + }, 54 + ), 55 + ), 56 + external_javascript: Some( 57 + ( 58 + "./gleam_stdlib.d.ts", 59 + "Dict", 60 + SrcSpan { 61 + start: 43, 62 + end: 95, 63 + }, 64 + ), 65 + ), 66 + }, 67 + ), 68 + target: None, 69 + }, 70 + ], 71 + names: Names { 72 + local_types: {}, 73 + imported_modules: {}, 74 + type_variables: {}, 75 + local_value_constructors: {}, 76 + reexport_aliases: {}, 77 + }, 78 + unused_definition_positions: {}, 79 + }, 80 + extra: ModuleExtra { 81 + module_comments: [], 82 + doc_comments: [], 83 + comments: [], 84 + empty_lines: [], 85 + new_lines: [ 86 + 0, 87 + 42, 88 + 95, 89 + 121, 90 + ], 91 + trailing_commas: [], 92 + }, 93 + }
+2 -2
compiler-core/src/parse/snapshots/gleam_core__parse__tests__external_attribute_with_non_fn_definition.snap
··· 1 1 --- 2 2 source: compiler-core/src/parse/tests.rs 3 - expression: "\n@external(erlang, \"module\", \"fun\")\npub type Fun\n" 3 + expression: "\n@external(erlang, \"module\", \"fun\")\npub type Fun = Fun\n" 4 4 --- 5 5 ----- SOURCE CODE 6 6 7 7 @external(erlang, "module", "fun") 8 - pub type Fun 8 + pub type Fun = Fun 9 9 10 10 11 11 ----- ERROR
+2
compiler-core/src/parse/snapshots/gleam_core__parse__tests__private_opaque_type_is_parsed.snap
··· 43 43 opaque: true, 44 44 parameters: [], 45 45 typed_parameters: [], 46 + external_erlang: None, 47 + external_javascript: None, 46 48 }, 47 49 ), 48 50 target: None,
+2
compiler-core/src/parse/snapshots/gleam_core__parse__tests__record_access_no_label.snap
··· 77 77 opaque: false, 78 78 parameters: [], 79 79 typed_parameters: [], 80 + external_erlang: None, 81 + external_javascript: None, 80 82 }, 81 83 ), 82 84 target: None,
+12 -1
compiler-core/src/parse/tests.rs
··· 818 818 } 819 819 820 820 #[test] 821 + fn external_attribute_with_custom_type() { 822 + assert_parse_module!( 823 + r#" 824 + @external(erlang, "gleam_stdlib", "dict") 825 + @external(javascript, "./gleam_stdlib.d.ts", "Dict") 826 + pub type Dict(key, value) 827 + "# 828 + ); 829 + } 830 + 831 + #[test] 821 832 fn external_attribute_with_non_fn_definition() { 822 833 assert_module_error!( 823 834 r#" 824 835 @external(erlang, "module", "fun") 825 - pub type Fun 836 + pub type Fun = Fun 826 837 "# 827 838 ); 828 839 }
+12 -1
compiler-core/src/type_/error.rs
··· 664 664 location: SrcSpan, 665 665 name: EcoString, 666 666 }, 667 + 668 + /// The `@external` annotation on custom types can only be used for external 669 + /// types, types with no constructors. 670 + /// 671 + ExternalTypeWithConstructors { 672 + location: SrcSpan, 673 + }, 667 674 } 668 675 669 676 #[derive(Debug, Clone, Copy, PartialEq, Eq)] ··· 1152 1159 VariantWithDeprecatedAnnotation, 1153 1160 JavaScriptUnalignedBitArray, 1154 1161 BoolAssert, 1162 + ExternalCustomType, 1155 1163 } 1156 1164 1157 1165 impl FeatureKind { ··· 1182 1190 FeatureKind::UnannotatedFloatSegment => Version::new(1, 10, 0), 1183 1191 1184 1192 FeatureKind::BoolAssert => Version::new(1, 11, 0), 1193 + 1194 + FeatureKind::ExternalCustomType => Version::new(1, 14, 0), 1185 1195 } 1186 1196 } 1187 1197 } ··· 1298 1308 | Error::DoubleVariableAssignmentInBitArray { location } 1299 1309 | Error::NonUtf8StringAssignmentInBitArray { location } 1300 1310 | Error::PrivateOpaqueType { location } 1301 - | Error::SrcImportingDevDependency { location, .. } => location.start, 1311 + | Error::SrcImportingDevDependency { location, .. } 1312 + | Error::ExternalTypeWithConstructors { location, .. } => location.start, 1302 1313 Error::UnknownLabels { unknown, .. } => { 1303 1314 unknown.iter().map(|(_, s)| s.start).min().unwrap_or(0) 1304 1315 }
+12
compiler-core/src/type_/tests/errors.rs
··· 3366 3366 }"# 3367 3367 ); 3368 3368 } 3369 + 3370 + #[test] 3371 + fn external_annotation_on_custom_type_with_constructors() { 3372 + assert_module_error!( 3373 + r#" 3374 + @external(erlang, "gleam_stdlib", "dict") 3375 + pub type Dict(key, value) { 3376 + Dict(pairs: List(#(key, value))) 3377 + } 3378 + "# 3379 + ); 3380 + }
+23
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__external_annotation_on_custom_type_with_constructors.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\n@external(erlang, \"gleam_stdlib\", \"dict\")\npub type Dict(key, value) {\n Dict(pairs: List(#(key, value)))\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + @external(erlang, "gleam_stdlib", "dict") 8 + pub type Dict(key, value) { 9 + Dict(pairs: List(#(key, value))) 10 + } 11 + 12 + 13 + ----- ERROR 14 + error: External type with constructors 15 + ┌─ /src/one/two.gleam:3:1 16 + 17 + 3 │ pub type Dict(key, value) { 18 + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ 19 + 20 + This type is annotated with the `@external` annotation, but it has 21 + constructors. The `@external` annotation is only for external types with no 22 + constructors. 23 + Hint: Remove the `@external` annotation
+24
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__external_annotation_on_custom_type_requires_v1_14.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\n@external(erlang, \"wibble\", \"wobble\")\npub type Wobble\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + @external(erlang, "wibble", "wobble") 8 + pub type Wobble 9 + 10 + 11 + ----- WARNING 12 + warning: Incompatible gleam version range 13 + ┌─ /src/warning/wrn.gleam:3:1 14 + 15 + 3 │ pub type Wobble 16 + │ ^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.14.0 17 + 18 + The `@external` annotation on custom types was introduced in version 19 + v1.14.0. But the Gleam version range specified in your `gleam.toml` would 20 + allow this code to run on an earlier version like v1.0.0, resulting in 21 + compilation errors! 22 + Hint: Remove the version constraint from your `gleam.toml` or update it to be: 23 + 24 + gleam = ">= 1.14.0"
+11
compiler-core/src/type_/tests/warnings.rs
··· 4531 4531 " 4532 4532 ); 4533 4533 } 4534 + 4535 + #[test] 4536 + fn external_annotation_on_custom_type_requires_v1_14() { 4537 + assert_warnings_with_gleam_version!( 4538 + Range::higher_than(Version::new(1, 0, 0)), 4539 + r#" 4540 + @external(erlang, "wibble", "wobble") 4541 + pub type Wobble 4542 + "#, 4543 + ); 4544 + }
+3
compiler-core/src/warning.rs
··· 1256 1256 "Use of unaligned bit arrays on the JavaScript target was" 1257 1257 } 1258 1258 FeatureKind::BoolAssert => "The bool `assert` statement was", 1259 + FeatureKind::ExternalCustomType => { 1260 + "The `@external` annotation on custom types was" 1261 + } 1259 1262 }; 1260 1263 1261 1264 Diagnostic {