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

Configure Feed

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

Implement external annotations for external types

author
Gears
committer
Louis Pilfold
date (Oct 27, 2025, 4:31 PM UTC) commit 31da0dae parent f13da6ae change-id omtozmlv
+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 {