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

Configure Feed

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

make constant records with no arguments not an error

author
Giacomo Cavalieri
committer
Louis Pilfold
date (May 21, 2026, 12:59 PM +0100) commit e210c37d parent 4dc957a9 change-id qykmyqmx
+378 -268
+29 -6
compiler-core/src/ast/constant.rs
··· 41 41 location: SrcSpan, 42 42 module: Option<(EcoString, SrcSpan)>, 43 43 name: EcoString, 44 - arguments: Vec<CallArg<Self>>, 44 + /// These are the arguments used when calling the record. 45 + /// If the record is not being called to build a value, then this will 46 + /// be `None`. A couple of examples: 47 + /// ```gleam 48 + /// pub const a = Wibble 49 + /// // arguments: None 50 + /// 51 + /// pub const b = Wibble() 52 + /// // arguments: Some(vec![]) 53 + /// 54 + /// pub const c = Wibble(1, 2) 55 + /// // arguments: Some(vec![1, 2]) 56 + /// ``` 57 + arguments: Option<Vec<CallArg<Self>>>, 45 58 type_: T, 46 59 field_map: Inferred<FieldMap>, 47 60 record_constructor: Option<Box<ValueConstructor>>, ··· 160 173 .unwrap_or(Located::Constant(self)), 161 174 Constant::Record { arguments, .. } => arguments 162 175 .iter() 176 + .flatten() 163 177 .find_map(|argument| argument.find_node(byte_index)) 164 178 .unwrap_or(Located::Constant(self)), 165 179 Constant::RecordUpdate { ··· 236 250 237 251 Constant::Record { arguments, .. } => arguments 238 252 .iter() 253 + .flatten() 239 254 .map(|argument| argument.value.referenced_variables()) 240 255 .fold(im::hashset![], im::HashSet::union), 241 256 ··· 347 362 (Some((one, _)), Some((other, _))) => one == other, 348 363 }; 349 364 350 - modules_are_equal 351 - && name == other_name 352 - && pairwise_all(arguments, other_arguments, |(one, other)| { 353 - one.label == other.label && one.value.syntactically_eq(&other.value) 354 - }) 365 + let arguments_are_equal = match (arguments, other_arguments) { 366 + (None, None) => true, 367 + (None, Some(_)) | (Some(_), None) => false, 368 + (Some(arguments), Some(other_arguments)) => { 369 + modules_are_equal 370 + && name == other_name 371 + && pairwise_all(arguments, other_arguments, |(one, other)| { 372 + one.label == other.label && one.value.syntactically_eq(&other.value) 373 + }) 374 + } 375 + }; 376 + 377 + modules_are_equal && arguments_are_equal 355 378 } 356 379 (Constant::Record { .. }, _) => false, 357 380
+3 -3
compiler-core/src/ast/visit.rs
··· 740 740 location: &'ast SrcSpan, 741 741 module: &'ast Option<(EcoString, SrcSpan)>, 742 742 name: &'ast EcoString, 743 - arguments: &'ast Vec<CallArg<TypedConstant>>, 743 + arguments: &'ast Option<Vec<CallArg<TypedConstant>>>, 744 744 type_: &'ast Arc<Type>, 745 745 field_map: &'ast Inferred<FieldMap>, 746 746 record_constructor: &'ast Option<Box<ValueConstructor>>, ··· 864 864 _location: &'a SrcSpan, 865 865 _module: &'a Option<(EcoString, SrcSpan)>, 866 866 _name: &'a EcoString, 867 - arguments: &'a Vec<CallArg<TypedConstant>>, 867 + arguments: &'a Option<Vec<CallArg<TypedConstant>>>, 868 868 _type_: &'a Arc<Type>, 869 869 _field_map: &'a Inferred<FieldMap>, 870 870 _record_constructor: &'a Option<Box<ValueConstructor>>, 871 871 ) { 872 - for argument in arguments { 872 + for argument in arguments.iter().flatten() { 873 873 v.visit_typed_constant(&argument.value) 874 874 } 875 875 }
+10 -8
compiler-core/src/ast_folder.rs
··· 1115 1115 location: SrcSpan, 1116 1116 module: Option<(EcoString, SrcSpan)>, 1117 1117 name: EcoString, 1118 - arguments: Vec<CallArg<UntypedConstant>>, 1118 + arguments: Option<Vec<CallArg<UntypedConstant>>>, 1119 1119 ) -> UntypedConstant { 1120 1120 Constant::Record { 1121 1121 location, ··· 1236 1236 field_map, 1237 1237 record_constructor, 1238 1238 } => { 1239 - let arguments = arguments 1240 - .into_iter() 1241 - .map(|mut argument| { 1242 - argument.value = self.fold_constant(argument.value); 1243 - argument 1244 - }) 1245 - .collect(); 1239 + let arguments = arguments.map(|arguments| { 1240 + arguments 1241 + .into_iter() 1242 + .map(|mut argument| { 1243 + argument.value = self.fold_constant(argument.value); 1244 + argument 1245 + }) 1246 + .collect() 1247 + }); 1246 1248 Constant::Record { 1247 1249 location, 1248 1250 module,
+1 -1
compiler-core/src/call_graph.rs
··· 485 485 } 486 486 487 487 Constant::Record { arguments, .. } => { 488 - for argument in arguments { 488 + for argument in arguments.iter().flatten() { 489 489 self.constant(&argument.value); 490 490 } 491 491 }
+3 -1
compiler-core/src/erlang.rs
··· 1798 1798 1799 1799 Constant::Record { 1800 1800 type_, arguments, .. 1801 - } if arguments.is_empty() => { 1801 + } if arguments.is_none() => { 1802 1802 let tag = literal 1803 1803 .constant_record_tag() 1804 1804 .expect("record without inferred constructor made it to code generation"); ··· 1819 1819 // Record updates are fully expanded during type checking, so we just handle arguments 1820 1820 let arguments_doc = arguments 1821 1821 .iter() 1822 + .flatten() 1822 1823 .map(|argument| const_inline(&argument.value, env)); 1823 1824 let tag = atom_string(to_snake_case(&tag)); 1824 1825 tuple(std::iter::once(tag).chain(arguments_doc)) ··· 3648 3649 3649 3650 TypedConstant::Record { arguments, .. } => arguments 3650 3651 .iter() 3652 + .flatten() 3651 3653 .for_each(|argument| find_referenced_private_functions(&argument.value, already_found)), 3652 3654 3653 3655 TypedConstant::StringConcatenation { left, right, .. } => {
+6 -6
compiler-core/src/format.rs
··· 515 515 516 516 Constant::Record { 517 517 name, 518 - arguments, 518 + arguments: None, 519 519 module: None, 520 520 .. 521 - } if arguments.is_empty() => name.to_doc(), 521 + } => name.to_doc(), 522 522 523 523 Constant::Record { 524 524 name, 525 - arguments, 525 + arguments: None, 526 526 module: Some((module, _)), 527 527 .. 528 - } if arguments.is_empty() => module.to_doc().append(".").append(name.as_str()), 528 + } => module.to_doc().append(".").append(name.as_str()), 529 529 530 530 Constant::Record { 531 531 name, 532 - arguments, 532 + arguments: Some(arguments), 533 533 module: None, 534 534 location, 535 535 .. ··· 545 545 546 546 Constant::Record { 547 547 name, 548 - arguments, 548 + arguments: Some(arguments), 549 549 module: Some((module, _)), 550 550 location, 551 551 ..
+13 -46
compiler-core/src/javascript/expression.rs
··· 2128 2128 // arguments then this is the constructor being referenced, not the 2129 2129 // function being called. 2130 2130 if let Some(arity) = type_.fn_arity() 2131 - && arguments.is_empty() 2131 + && arguments.is_none() 2132 2132 && arity != 0 2133 2133 { 2134 2134 let arity = arity as u16; 2135 2135 return record_constructor(type_.clone(), None, name, arity, self.tracker); 2136 2136 } 2137 2137 2138 - // Record updates are fully expanded during type checking, so we just handle arguments 2138 + // Otherwise we're always constructing a record! Even if there's 2139 + // no argument list: 2140 + // ```gleam 2141 + // pub type Wibble { Wibble } 2142 + // pub const wibble = Wibble // <- here we're constructing the record! 2143 + // ``` 2144 + // 2145 + // Record updates are fully expanded during type checking, so we 2146 + // just handle arguments 2139 2147 let field_values = arguments 2140 2148 .iter() 2149 + .flatten() 2141 2150 .map(|argument| self.constant_expression(context, &argument.value)) 2142 2151 .collect_vec(); 2143 2152 ··· 2478 2487 } 2479 2488 Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(), 2480 2489 2481 - Constant::Record { 2482 - arguments, 2483 - module, 2484 - name, 2485 - type_, 2486 - .. 2487 - } => { 2488 - let tag = expression 2489 - .constant_record_tag() 2490 - .expect("record without inferred constructor made it to code generation"); 2491 - 2492 - if module.is_none() && type_.is_result() { 2493 - if tag == "Ok" { 2494 - self.tracker.ok_used = true; 2495 - } else { 2496 - self.tracker.error_used = true; 2497 - } 2498 - } 2499 - 2500 - // If there's no arguments and the type is a function that takes 2501 - // arguments then this is the constructor being referenced, not the 2502 - // function being called. 2503 - if let Some(arity) = type_.fn_arity() 2504 - && arguments.is_empty() 2505 - && arity != 0 2506 - { 2507 - let arity = arity as u16; 2508 - return record_constructor(type_.clone(), None, name, arity, self.tracker); 2509 - } 2510 - 2511 - // Record updates are fully expanded during type checking, so we just 2512 - // handle arguments 2513 - let field_values = arguments 2514 - .iter() 2515 - .map(|argument| self.guard_constant_expression(&argument.value)) 2516 - .collect_vec(); 2517 - construct_record( 2518 - module.as_ref().map(|(module, _)| module.as_str()), 2519 - name, 2520 - field_values, 2521 - ) 2522 - } 2523 - 2524 2490 Constant::BitArray { segments, .. } => { 2525 2491 self.constant_bit_array(segments, Context::Guard) 2526 2492 } 2527 2493 2528 2494 Constant::Var { name, .. } => self.local_var(name).to_doc(), 2529 2495 2530 - Constant::Int { .. } 2496 + Constant::Record { .. } 2497 + | Constant::Int { .. } 2531 2498 | Constant::Float { .. } 2532 2499 | Constant::String { .. } 2533 2500 | Constant::List { .. }
+18 -16
compiler-core/src/metadata.rs
··· 342 342 location, 343 343 module, 344 344 name, 345 - arguments: arguments 346 - .into_iter() 347 - .map( 348 - |CallArg { 349 - label, 350 - location, 351 - value, 352 - implicit, 353 - }| CallArg { 354 - label, 355 - location, 356 - value: self.constant(value), 357 - implicit, 358 - }, 359 - ) 360 - .collect(), 345 + arguments: arguments.map(|arguments| { 346 + arguments 347 + .into_iter() 348 + .map( 349 + |CallArg { 350 + label, 351 + location, 352 + value, 353 + implicit, 354 + }| CallArg { 355 + label, 356 + location, 357 + value: self.constant(value), 358 + implicit, 359 + }, 360 + ) 361 + .collect() 362 + }), 361 363 type_: self.type_(type_), 362 364 field_map, 363 365 record_constructor: record_constructor
+2 -2
compiler-core/src/metadata/tests.rs
··· 1169 1169 location: Default::default(), 1170 1170 module: None, 1171 1171 name: "".into(), 1172 - arguments: vec![ 1172 + arguments: Some(vec![ 1173 1173 CallArg { 1174 1174 implicit: None, 1175 1175 label: None, ··· 1190 1190 int_value: 1.into(), 1191 1191 }, 1192 1192 }, 1193 - ], 1193 + ]), 1194 1194 type_: type_::int(), 1195 1195 field_map: Inferred::Unknown, 1196 1196 record_constructor: None,
+2 -9
compiler-core/src/parse.rs
··· 3575 3575 "a constant record argument", 3576 3576 )?; 3577 3577 3578 - if arguments.is_empty() { 3579 - return parse_error( 3580 - ParseErrorType::ConstantRecordConstructorNoArguments, 3581 - SrcSpan::new(par_s, par_e), 3582 - ); 3583 - } 3584 - 3585 3578 Ok(Some(Constant::Record { 3586 3579 location: SrcSpan { start, end: par_e }, 3587 3580 module, 3588 3581 name, 3589 - arguments, 3582 + arguments: Some(arguments), 3590 3583 type_: (), 3591 3584 field_map: Inferred::Unknown, 3592 3585 record_constructor: None, ··· 3597 3590 location: SrcSpan { start, end }, 3598 3591 module, 3599 3592 name, 3600 - arguments: vec![], 3593 + arguments: None, 3601 3594 type_: (), 3602 3595 field_map: Inferred::Unknown, 3603 3596 record_constructor: None,
+1 -9
compiler-core/src/parse/error.rs
··· 122 122 }, 123 123 CallInClauseGuard, // case x { _ if f() -> 1 } 124 124 IfExpression, 125 - ConstantRecordConstructorNoArguments, // const x = Record() 126 - TypeDefinitionNoArguments, // pub type Wibble() { ... } 125 + TypeDefinitionNoArguments, // pub type Wibble() { ... } 127 126 UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant 128 127 // a Python-like import was written, such as `import gleam.io`, instead of `import gleam/io` 129 128 IncorrectImportModuleSeparator { ··· 675 674 .join("\n"), 676 675 hint: None, 677 676 label_text: "Gleam doesn't have if expressions".into(), 678 - extra_labels: vec![], 679 - }, 680 - 681 - ParseErrorType::ConstantRecordConstructorNoArguments => ParseErrorDetails { 682 - text: "A record must be passed arguments when constructed.".into(), 683 - hint: None, 684 - label_text: "I was expecting arguments here".into(), 685 677 extra_labels: vec![], 686 678 }, 687 679
+17
compiler-core/src/parse/snapshots/gleam_core__parse__tests__calling_module_constant_as_constructor.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "\npub type Wibble { Wibble(Int) }\npub const wibble = Wibble\npub const a = wibble(1)\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub type Wibble { Wibble(Int) } 8 + pub const wibble = Wibble 9 + pub const a = wibble(1) 10 + 11 + 12 + ----- ERROR 13 + error: Syntax error 14 + ┌─ /src/parse/error.gleam:4:15 15 + 16 + 4 │ pub const a = wibble(1) 17 + │ ^^^^^^^ Functions can only be called within other functions
+35 -33
compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_record_update_all_fields.snap
··· 174 174 }, 175 175 module: None, 176 176 name: "Person", 177 - arguments: [ 178 - CallArg { 179 - label: None, 180 - location: SrcSpan { 181 - start: 85, 182 - end: 92, 183 - }, 184 - value: String { 177 + arguments: Some( 178 + [ 179 + CallArg { 180 + label: None, 185 181 location: SrcSpan { 186 182 start: 85, 187 183 end: 92, 188 184 }, 189 - value: "Alice", 190 - }, 191 - implicit: None, 192 - }, 193 - CallArg { 194 - label: None, 195 - location: SrcSpan { 196 - start: 94, 197 - end: 96, 185 + value: String { 186 + location: SrcSpan { 187 + start: 85, 188 + end: 92, 189 + }, 190 + value: "Alice", 191 + }, 192 + implicit: None, 198 193 }, 199 - value: Int { 194 + CallArg { 195 + label: None, 200 196 location: SrcSpan { 201 197 start: 94, 202 198 end: 96, 203 199 }, 204 - value: "30", 205 - int_value: 30, 206 - }, 207 - implicit: None, 208 - }, 209 - CallArg { 210 - label: None, 211 - location: SrcSpan { 212 - start: 98, 213 - end: 106, 200 + value: Int { 201 + location: SrcSpan { 202 + start: 94, 203 + end: 96, 204 + }, 205 + value: "30", 206 + int_value: 30, 207 + }, 208 + implicit: None, 214 209 }, 215 - value: String { 210 + CallArg { 211 + label: None, 216 212 location: SrcSpan { 217 213 start: 98, 218 214 end: 106, 219 215 }, 220 - value: "London", 216 + value: String { 217 + location: SrcSpan { 218 + start: 98, 219 + end: 106, 220 + }, 221 + value: "London", 222 + }, 223 + implicit: None, 221 224 }, 222 - implicit: None, 223 - }, 224 - ], 225 + ], 226 + ), 225 227 type_: (), 226 228 field_map: Unknown, 227 229 record_constructor: None,
+25 -23
compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_record_update_basic.snap
··· 140 140 }, 141 141 module: None, 142 142 name: "Person", 143 - arguments: [ 144 - CallArg { 145 - label: None, 146 - location: SrcSpan { 147 - start: 72, 148 - end: 79, 149 - }, 150 - value: String { 143 + arguments: Some( 144 + [ 145 + CallArg { 146 + label: None, 151 147 location: SrcSpan { 152 148 start: 72, 153 149 end: 79, 154 150 }, 155 - value: "Alice", 156 - }, 157 - implicit: None, 158 - }, 159 - CallArg { 160 - label: None, 161 - location: SrcSpan { 162 - start: 81, 163 - end: 83, 151 + value: String { 152 + location: SrcSpan { 153 + start: 72, 154 + end: 79, 155 + }, 156 + value: "Alice", 157 + }, 158 + implicit: None, 164 159 }, 165 - value: Int { 160 + CallArg { 161 + label: None, 166 162 location: SrcSpan { 167 163 start: 81, 168 164 end: 83, 169 165 }, 170 - value: "30", 171 - int_value: 30, 166 + value: Int { 167 + location: SrcSpan { 168 + start: 81, 169 + end: 83, 170 + }, 171 + value: "30", 172 + int_value: 30, 173 + }, 174 + implicit: None, 172 175 }, 173 - implicit: None, 174 - }, 175 - ], 176 + ], 177 + ), 176 178 type_: (), 177 179 field_map: Unknown, 178 180 record_constructor: None,
+25 -23
compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_record_update_only.snap
··· 140 140 }, 141 141 module: None, 142 142 name: "Person", 143 - arguments: [ 144 - CallArg { 145 - label: None, 146 - location: SrcSpan { 147 - start: 72, 148 - end: 79, 149 - }, 150 - value: String { 143 + arguments: Some( 144 + [ 145 + CallArg { 146 + label: None, 151 147 location: SrcSpan { 152 148 start: 72, 153 149 end: 79, 154 150 }, 155 - value: "Alice", 156 - }, 157 - implicit: None, 158 - }, 159 - CallArg { 160 - label: None, 161 - location: SrcSpan { 162 - start: 81, 163 - end: 83, 151 + value: String { 152 + location: SrcSpan { 153 + start: 72, 154 + end: 79, 155 + }, 156 + value: "Alice", 157 + }, 158 + implicit: None, 164 159 }, 165 - value: Int { 160 + CallArg { 161 + label: None, 166 162 location: SrcSpan { 167 163 start: 81, 168 164 end: 83, 169 165 }, 170 - value: "30", 171 - int_value: 30, 166 + value: Int { 167 + location: SrcSpan { 168 + start: 81, 169 + end: 83, 170 + }, 171 + value: "30", 172 + int_value: 30, 173 + }, 174 + implicit: None, 172 175 }, 173 - implicit: None, 174 - }, 175 - ], 176 + ], 177 + ), 176 178 type_: (), 177 179 field_map: Unknown, 178 180 record_constructor: None,
+71
compiler-core/src/parse/snapshots/gleam_core__parse__tests__constant_record_with_empty_arguments_is_record.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: pub const wibble = Wibble() 4 + --- 5 + Parsed { 6 + module: Module { 7 + name: "", 8 + documentation: [], 9 + type_info: (), 10 + definitions: [ 11 + TargetedDefinition { 12 + definition: ModuleConstant( 13 + ModuleConstant { 14 + documentation: None, 15 + location: SrcSpan { 16 + start: 0, 17 + end: 16, 18 + }, 19 + publicity: Public, 20 + name: "wibble", 21 + name_location: SrcSpan { 22 + start: 10, 23 + end: 16, 24 + }, 25 + annotation: None, 26 + value: Record { 27 + location: SrcSpan { 28 + start: 19, 29 + end: 27, 30 + }, 31 + module: None, 32 + name: "Wibble", 33 + arguments: Some( 34 + [], 35 + ), 36 + type_: (), 37 + field_map: Unknown, 38 + record_constructor: None, 39 + }, 40 + type_: (), 41 + deprecation: NotDeprecated, 42 + implementations: Implementations { 43 + gleam: true, 44 + can_run_on_erlang: true, 45 + can_run_on_javascript: true, 46 + uses_erlang_externals: false, 47 + uses_javascript_externals: false, 48 + }, 49 + }, 50 + ), 51 + target: None, 52 + }, 53 + ], 54 + names: Names { 55 + local_types: {}, 56 + imported_modules: {}, 57 + type_variables: {}, 58 + local_value_constructors: {}, 59 + reexport_aliases: {}, 60 + }, 61 + unused_definition_positions: {}, 62 + }, 63 + extra: ModuleExtra { 64 + module_comments: [], 65 + doc_comments: [], 66 + comments: [], 67 + empty_lines: [], 68 + new_lines: [], 69 + trailing_commas: [], 70 + }, 71 + }
+16 -14
compiler-core/src/parse/tests.rs
··· 1509 1509 } 1510 1510 1511 1511 #[test] 1512 + fn constant_record_with_empty_arguments_is_record() { 1513 + assert_parse_module!("pub const wibble = Wibble()"); 1514 + } 1515 + 1516 + #[test] 1517 + fn calling_module_constant_as_constructor() { 1518 + assert_module_error!( 1519 + " 1520 + pub type Wibble { Wibble(Int) } 1521 + pub const wibble = Wibble 1522 + pub const a = wibble(1) 1523 + " 1524 + ); 1525 + } 1526 + 1527 + #[test] 1512 1528 fn invalid_label_shorthand() { 1513 1529 assert_module_error!( 1514 1530 " ··· 1775 1791 } 1776 1792 } 1777 1793 "# 1778 - ); 1779 - } 1780 - 1781 - // https://github.com/gleam-lang/gleam/issues/3730 1782 - #[test] 1783 - fn missing_constructor_arguments() { 1784 - assert_module_error!( 1785 - " 1786 - pub type A { 1787 - A(Int) 1788 - } 1789 - 1790 - const a = A() 1791 - " 1792 1794 ); 1793 1795 } 1794 1796
+33 -63
compiler-core/src/type_/expression.rs
··· 4040 4040 && let ValueConstructorVariant::Record { name, .. } = 4041 4041 resolved_record_constructor.variant 4042 4042 { 4043 - (arguments, name) 4043 + (arguments.unwrap_or(vec![]), name) 4044 4044 } else { 4045 4045 self.problems.error(convert_unify_error( 4046 4046 UnifyError::CouldNotUnify { ··· 4183 4183 module, 4184 4184 location, 4185 4185 name, 4186 - arguments: final_arguments, 4186 + arguments: Some(final_arguments), 4187 4187 type_: expected_type, 4188 4188 field_map: Inferred::Known(field_map), 4189 4189 record_constructor: Some(Box::new(constructor)), ··· 4196 4196 name, 4197 4197 arguments, 4198 4198 .. 4199 - } if arguments.is_empty() => { 4200 - let constructor = match self.infer_value_constructor( 4201 - &module, 4202 - &name, 4203 - &location, 4204 - ValueUsage::Other, 4205 - ) { 4206 - Ok(constructor) => constructor, 4207 - Err(error) => { 4208 - self.problems.error(error); 4209 - return self.new_invalid_constant(location); 4210 - } 4211 - }; 4212 - 4213 - let field_map = match &constructor.variant { 4214 - ValueConstructorVariant::Record { field_map, .. } => match field_map { 4215 - Some(field_map) => Inferred::Known(field_map.clone()), 4216 - None => Inferred::Unknown, 4217 - }, 4218 - 4219 - ValueConstructorVariant::ModuleFn { .. } 4220 - | ValueConstructorVariant::LocalVariable { .. } => { 4221 - self.problems 4222 - .error(Error::NonLocalClauseGuardVariable { location, name }); 4223 - return self.new_invalid_constant(location); 4224 - } 4225 - 4226 - // TODO: remove this clone. Could use an rc instead 4227 - ValueConstructorVariant::ModuleConstant { literal, .. } => { 4228 - return literal.clone(); 4229 - } 4230 - }; 4231 - 4232 - Constant::Record { 4233 - module, 4234 - location, 4235 - name, 4236 - arguments: vec![], 4237 - type_: constructor.type_.clone(), 4238 - field_map, 4239 - record_constructor: Some(Box::new(constructor)), 4240 - } 4241 - } 4242 - 4243 - Constant::Record { 4244 - module, 4245 - location, 4246 - name, 4247 - arguments, 4248 - .. 4249 4199 } => self.infer_constant_record(module, location, name, arguments), 4250 4200 4251 4201 Constant::Var { ··· 4360 4310 module: Option<(EcoString, SrcSpan)>, 4361 4311 location: SrcSpan, 4362 4312 name: EcoString, 4363 - mut arguments: Vec<CallArg<UntypedConstant>>, 4313 + arguments: Option<Vec<CallArg<UntypedConstant>>>, 4364 4314 ) -> Constant<Arc<Type>> { 4365 4315 // We start by inferring the value constructor. If we can't do that we 4366 4316 // immediately fail and return an invalid node. 4367 4317 // TODO: in future we might want to make this more fault tolerant and 4368 4318 // still check the arguments even if the constructor itself cannot 4369 4319 // be inferred, like we do for expressions! 4370 - let constructor = match self.infer_value_constructor( 4371 - &module, 4372 - &name, 4373 - &location, 4374 - ValueUsage::Call { 4320 + 4321 + // The usage counts as a call only if there's actually an arguments list! 4322 + // `Wibble()` and `Wibble(1, 2)` are calls, but `Wibble` is not! 4323 + let usage = arguments 4324 + .as_ref() 4325 + .map_or(ValueUsage::Other, |arguments| ValueUsage::Call { 4375 4326 arity: arguments.len(), 4376 - }, 4377 - ) { 4327 + }); 4328 + 4329 + let constructor = match self.infer_value_constructor(&module, &name, &location, usage) { 4378 4330 Ok(constructor) => constructor, 4379 4331 Err(error) => { 4380 4332 self.problems.error(error); ··· 4393 4345 } 4394 4346 4395 4347 // TODO: remove this clone. Could be an rc instead 4396 - ValueConstructorVariant::ModuleConstant { literal, .. } => { 4397 - return literal.clone(); 4348 + ValueConstructorVariant::ModuleConstant { .. } => { 4349 + unreachable!("module constant called as a record is a syntax error") 4398 4350 } 4351 + }; 4352 + 4353 + // If the arguments are none, then there's nothing else left to type, we 4354 + // can just return. 4355 + // Otherwise we'll have to go on and also check the arguments. 4356 + let Some(mut arguments) = arguments else { 4357 + return Constant::Record { 4358 + module, 4359 + location, 4360 + name, 4361 + arguments: None, 4362 + type_: constructor.type_.clone(), 4363 + field_map: match field_map { 4364 + Some(field_map) => Inferred::Known(field_map), 4365 + None => Inferred::Unknown, 4366 + }, 4367 + record_constructor: Some(Box::new(constructor)), 4368 + }; 4399 4369 }; 4400 4370 4401 4371 // This is basically the same code as do_infer_call_with_known_fun() ··· 4510 4480 module, 4511 4481 location, 4512 4482 name, 4513 - arguments: typed_arguments, 4483 + arguments: Some(typed_arguments), 4514 4484 type_: expected_return, 4515 4485 field_map: match field_map { 4516 4486 Some(field_map) => Inferred::Known(field_map),
+24
compiler-core/src/type_/tests/errors.rs
··· 4276 4276 "# 4277 4277 ); 4278 4278 } 4279 + 4280 + // https://github.com/gleam-lang/gleam/issues/3730 4281 + #[test] 4282 + fn missing_constructor_arguments() { 4283 + assert_module_error!( 4284 + " 4285 + pub type A { 4286 + A(Int) 4287 + } 4288 + 4289 + const a = A() 4290 + " 4291 + ); 4292 + } 4293 + 4294 + #[test] 4295 + fn constant_calling_constructor_with_no_arguments() { 4296 + assert_module_error!( 4297 + " 4298 + pub type A { A } 4299 + const a = A() 4300 + " 4301 + ); 4302 + }
+20
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__constant_calling_constructor_with_no_arguments.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\npub type A { A }\nconst a = A()\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub type A { A } 8 + const a = A() 9 + 10 + 11 + ----- ERROR 12 + error: Type mismatch 13 + ┌─ /src/one/two.gleam:3:11 14 + 15 + 3 │ const a = A() 16 + │ ^^^ 17 + 18 + This value is being called as a function but its type is: 19 + 20 + A
+19
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__missing_constructor_arguments.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\npub type A {\n A(Int)\n}\n\nconst a = A()\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub type A { 8 + A(Int) 9 + } 10 + 11 + const a = A() 12 + 13 + 14 + ----- ERROR 15 + error: Incorrect arity 16 + ┌─ /src/one/two.gleam:6:11 17 + 18 + 6 │ const a = A() 19 + │ ^^^ Expected 1 argument, got 0
+4 -4
language-server/src/code_action.rs
··· 1915 1915 location: &'ast SrcSpan, 1916 1916 module: &'ast Option<(EcoString, SrcSpan)>, 1917 1917 name: &'ast EcoString, 1918 - arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 1918 + arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 1919 1919 type_: &'ast Arc<Type>, 1920 1920 field_map: &'ast Inferred<FieldMap>, 1921 1921 record_constructor: &'ast Option<Box<ValueConstructor>>, ··· 2150 2150 location: &'ast SrcSpan, 2151 2151 module: &'ast Option<(EcoString, SrcSpan)>, 2152 2152 name: &'ast EcoString, 2153 - arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 2153 + arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2154 2154 type_: &'ast Arc<Type>, 2155 2155 field_map: &'ast Inferred<FieldMap>, 2156 2156 record_constructor: &'ast Option<Box<ValueConstructor>>, ··· 2385 2385 location: &'ast SrcSpan, 2386 2386 module: &'ast Option<(EcoString, SrcSpan)>, 2387 2387 name: &'ast EcoString, 2388 - arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 2388 + arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2389 2389 type_: &'ast Arc<Type>, 2390 2390 field_map: &'ast Inferred<FieldMap>, 2391 2391 record_constructor: &'ast Option<Box<ValueConstructor>>, ··· 2618 2618 location: &'ast SrcSpan, 2619 2619 module: &'ast Option<(EcoString, SrcSpan)>, 2620 2620 name: &'ast EcoString, 2621 - arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 2621 + arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2622 2622 type_: &'ast Arc<Type>, 2623 2623 field_map: &'ast Inferred<FieldMap>, 2624 2624 record_constructor: &'ast Option<Box<ValueConstructor>>,
+1 -1
language-server/src/reference.rs
··· 943 943 location: &'ast SrcSpan, 944 944 module: &'ast Option<(EcoString, SrcSpan)>, 945 945 name: &'ast EcoString, 946 - arguments: &'ast Vec<ast::CallArg<ast::TypedConstant>>, 946 + arguments: &'ast Option<Vec<ast::CallArg<ast::TypedConstant>>>, 947 947 type_: &'ast std::sync::Arc<Type>, 948 948 field_map: &'ast analyse::Inferred<gleam_core::type_::FieldMap>, 949 949 record_constructor: &'ast Option<Box<ValueConstructor>>,