A code generation library for Gleam
2

Configure Feed

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

Implement labelled arguments in calls

Gears (Oct 17, 2025, 6:03 PM +0100) c8c7e627 675b1283

+430 -12
-1
TODO.md
··· 7 7 - Bit arrays* 8 8 - Case expressions 9 9 - Field access 10 - - Labelled arguments when calling functions 11 10 - Let assert 12 11 - Let with patterns 13 12 - Pipes*
+15
birdie_snapshots/labelled_call.accepted
··· 1 + --- 2 + version: 1.4.1 3 + title: labelled_call 4 + file: ./test/trick_test.gleam 5 + test_name: labelled_call_test 6 + --- 7 + fn wibble(left left: Int, right right: Int, condition condition: Bool) -> Bool { 8 + left == right && condition 9 + } 10 + 11 + fn main() -> Bool { 12 + wibble(True, right: 2, left: 1) 13 + } 14 + 15 +
+261 -10
src/trick.gleam
··· 6 6 import gleam/list 7 7 import gleam/option.{type Option, None, Some} 8 8 import gleam/result 9 + import gleam/set.{type Set} 9 10 import gleam/string 10 11 import lazy_const 11 12 import splitter ··· 33 34 InvalidCall(type_: Type) 34 35 IncorrectNumberOfArguments(expected: Int, got: Int) 35 36 UnlabelledParameterAfterLabelledParameter 37 + UnexpectedLabelledArgument(label: String) 38 + UnknownLabel(label: String, available_labels: List(String)) 39 + DuplicateLabel(label: String) 36 40 } 37 41 38 42 type Compiled { ··· 43 47 Custom(module: String, name: String, generics: List(Type)) 44 48 TypeVariable(id: Int) 45 49 Tuple(elements: List(Type)) 46 - Function(parameters: List(Type), return: Type) 50 + Function(parameters: List(Type), return: Type, field_map: Option(FieldMap)) 51 + } 52 + 53 + pub type FieldMap { 54 + FieldMap(arity: Int, fields: Dict(String, Int)) 47 55 } 48 56 49 57 type State { ··· 230 238 } 231 239 } 232 240 } 233 - Function(parameters: p1, return: r1), Function(parameters: p2, return: r2) -> 241 + Function(parameters: p1, return: r1, field_map: _), 242 + Function(parameters: p2, return: r2, field_map: _) 243 + -> 234 244 case list.strict_zip(p1, p2) { 235 245 Error(_) -> Error(mismatch) 236 246 Ok(parameters) -> { ··· 249 259 case do_unify(state, r1, r2) { 250 260 Error(error) -> Error(error) 251 261 Ok(#(state, return)) -> 252 - Ok(#(state, Function(list.reverse(parameters), return))) 262 + Ok(#(state, Function(list.reverse(parameters), return, None))) 253 263 } 254 264 } 255 265 } ··· 882 892 parameter.type_ 883 893 }), 884 894 return: return_type, 895 + field_map: None, 885 896 ) 886 897 887 898 [doc.from_string("fn"), parameter_list, doc.from_string(" "), body_doc] ··· 895 906 ) -> FunctionBuilder(Labelled) { 896 907 use state, name, return_type, parameter_types <- FunctionBuilder 897 908 898 - let type_ = Function(parameters: parameter_types, return: return_type) 909 + let type_ = 910 + Function(parameters: parameter_types, return: return_type, field_map: None) 899 911 900 912 let expression = return(Compiled(doc.from_string(name), type_)) 901 913 ··· 989 1001 arguments: List(Expression(a)), 990 1002 ) -> Expression(Variable) { 991 1003 use function <- compile(function) 1004 + 992 1005 use arguments <- compile_expressions(arguments) 993 1006 994 1007 use called_type <- then(unwrap_type(function.type_)) 995 1008 996 1009 case called_type { 997 1010 Custom(..) | Tuple(..) -> error(InvalidCall(called_type)) 998 - Function(parameters:, return: return_type) -> 1011 + Function(parameters:, return: return_type, field_map: _) -> 999 1012 case list.strict_zip(arguments, parameters) { 1000 1013 Error(Nil) -> { 1001 1014 let expected_length = list.length(parameters) ··· 1019 1032 use return_type <- then(type_variable) 1020 1033 1021 1034 let function_type = 1022 - Function(parameters: parameter_types, return: return_type) 1035 + Function( 1036 + parameters: parameter_types, 1037 + return: return_type, 1038 + field_map: None, 1039 + ) 1023 1040 use _ <- try(unify(called_type, function_type)) 1024 1041 1025 1042 call_doc(arguments, function, return_type) ··· 1069 1086 1070 1087 case called_type { 1071 1088 Custom(..) | Tuple(..) -> error(InvalidCall(called_type)) 1072 - Function(parameters:, return: return_type) -> 1089 + Function(parameters:, return: return_type, field_map: _) -> 1073 1090 case list.strict_zip(argument_types, parameters) { 1074 1091 Error(Nil) -> { 1075 1092 let expected_length = list.length(parameters) ··· 1092 1109 use return_type <- then(type_variable) 1093 1110 1094 1111 let function_type = 1095 - Function(parameters: argument_types, return: return_type) 1112 + Function( 1113 + parameters: argument_types, 1114 + return: return_type, 1115 + field_map: None, 1116 + ) 1096 1117 use _ <- try(unify(called_type, function_type)) 1097 1118 1098 1119 capture_doc(function, before, after, parameter_type, return_type) ··· 1107 1128 parameter_type: Type, 1108 1129 return_type: Type, 1109 1130 ) -> Expression(a) { 1110 - let type_ = Function(parameters: [parameter_type], return: return_type) 1131 + let type_ = 1132 + Function(parameters: [parameter_type], return: return_type, field_map: None) 1111 1133 1112 1134 [ 1113 1135 doc.break("(", "("), ··· 1184 1206 1185 1207 use return_type <- try(unify(body.type_, return_type)) 1186 1208 1209 + use #(fields, arity) <- try( 1210 + pure( 1211 + list.try_fold(function.parameters, #(dict.new(), 0), fn(pair, parameter) { 1212 + let #(map, index) = pair 1213 + case parameter.label { 1214 + None -> Ok(#(map, index + 1)) 1215 + Some(label) -> 1216 + case dict.get(map, label) { 1217 + Error(_) -> Ok(#(dict.insert(map, label, index), index + 1)) 1218 + Ok(_) -> Error(DuplicateLabel(label:)) 1219 + } 1220 + } 1221 + }), 1222 + ), 1223 + ) 1224 + 1225 + let field_map = FieldMap(arity:, fields:) 1226 + 1187 1227 let type_ = 1188 1228 Function( 1189 1229 parameters: list.map(function.parameters, fn(parameter) { 1190 1230 parameter.type_ 1191 1231 }), 1192 1232 return: return_type, 1233 + field_map: Some(field_map), 1193 1234 ) 1194 1235 1195 1236 let function_name = return(Compiled(doc.from_string(name), type_)) ··· 1256 1297 <> string.join(list.map(generics, print_type), ", ") 1257 1298 <> ")" 1258 1299 } 1259 - Function(parameters:, return:) -> 1300 + Function(parameters:, return:, field_map: _) -> 1260 1301 "fn(" 1261 1302 <> string.join(list.map(parameters, print_type), ", ") 1262 1303 <> ") -> " ··· 1312 1353 _ -> "z" 1313 1354 } 1314 1355 } 1356 + 1357 + pub opaque type Argument { 1358 + Argument(label: Option(String), value: Expression(Variable)) 1359 + } 1360 + 1361 + pub fn argument(label: Option(String), value: Expression(Variable)) -> Argument { 1362 + Argument(label, value) 1363 + } 1364 + 1365 + type CompiledArgument { 1366 + CompiledArgument(label: Option(String), value: Compiled) 1367 + } 1368 + 1369 + pub fn labelled_call( 1370 + function: Expression(a), 1371 + arguments: List(Argument), 1372 + ) -> Expression(Variable) { 1373 + use function <- compile(function) 1374 + use arguments <- fold_list( 1375 + arguments, 1376 + fn(state) { #(state, []) }, 1377 + fn(arguments, argument) { 1378 + fn(state) { 1379 + use #(state, value) <- result.map(argument.value.compile(state)) 1380 + #(state, [CompiledArgument(label: argument.label, value:), ..arguments]) 1381 + } 1382 + }, 1383 + ) 1384 + let arguments = list.reverse(arguments) 1385 + 1386 + use called_type <- then(unwrap_type(function.type_)) 1387 + 1388 + let field_map = case called_type { 1389 + Custom(..) | Tuple(..) | TypeVariable(..) -> None 1390 + Function(field_map:, ..) -> field_map 1391 + } 1392 + 1393 + use argument_types <- try(case field_map { 1394 + None -> assert_no_labelled_arguments(arguments) 1395 + Some(field_map) -> pure(reorder(arguments, field_map)) 1396 + }) 1397 + 1398 + use called_type <- then(unwrap_type(function.type_)) 1399 + 1400 + case called_type { 1401 + Custom(..) | Tuple(..) -> error(InvalidCall(called_type)) 1402 + Function(parameters:, return: return_type, field_map: _) -> 1403 + case list.strict_zip(argument_types, parameters) { 1404 + Error(Nil) -> { 1405 + let expected_length = list.length(parameters) 1406 + let argument_length = list.length(argument_types) 1407 + error(IncorrectNumberOfArguments( 1408 + expected: expected_length, 1409 + got: argument_length, 1410 + )) 1411 + } 1412 + Ok(zipped) -> { 1413 + use <- try_each(zipped, fn(pair) { 1414 + let #(arg, param) = pair 1415 + unify(arg, with: param) 1416 + }) 1417 + 1418 + labelled_call_doc(arguments, function, return_type) 1419 + } 1420 + } 1421 + TypeVariable(_) -> { 1422 + use return_type <- then(type_variable) 1423 + 1424 + let function_type = 1425 + Function( 1426 + parameters: argument_types, 1427 + return: return_type, 1428 + field_map: None, 1429 + ) 1430 + use _ <- try(unify(called_type, function_type)) 1431 + 1432 + labelled_call_doc(arguments, function, return_type) 1433 + } 1434 + } 1435 + } 1436 + 1437 + fn labelled_call_doc( 1438 + arguments: List(CompiledArgument), 1439 + function: Compiled, 1440 + return_type: Type, 1441 + ) -> Expression(a) { 1442 + [ 1443 + doc.break("(", "("), 1444 + arguments 1445 + |> list.map(fn(arg) { 1446 + case arg.label { 1447 + None -> arg.value.document 1448 + Some(label) -> 1449 + doc.concat([ 1450 + doc.from_string(label), 1451 + doc.from_string(": "), 1452 + arg.value.document, 1453 + ]) 1454 + } 1455 + }) 1456 + |> doc.join(doc.break(", ", ",")), 1457 + ] 1458 + |> doc.concat 1459 + |> doc.nest(indent) 1460 + |> doc.prepend(function.document) 1461 + |> doc.append(doc.break("", ",")) 1462 + |> doc.append(doc.from_string(")")) 1463 + |> doc.group 1464 + |> Compiled(return_type) 1465 + |> return 1466 + } 1467 + 1468 + fn reorder( 1469 + arguments: List(CompiledArgument), 1470 + field_map: FieldMap, 1471 + ) -> Result(List(Type), Error) { 1472 + // TODO: Handle incorrect arity and duplicate labels 1473 + let argument_count = list.length(arguments) 1474 + use <- bool.guard( 1475 + argument_count != field_map.arity, 1476 + Error(IncorrectNumberOfArguments( 1477 + expected: field_map.arity, 1478 + got: argument_count, 1479 + )), 1480 + ) 1481 + 1482 + use #(unlabelled_fields, labelled_fields) <- result.map(split_arguments( 1483 + arguments, 1484 + field_map, 1485 + dict.new(), 1486 + [], 1487 + set.new(), 1488 + )) 1489 + 1490 + reorder_arguments(unlabelled_fields, labelled_fields, 0, []) 1491 + } 1492 + 1493 + fn reorder_arguments( 1494 + unlabelled_fields: List(Type), 1495 + labelled_fields: Dict(Int, Type), 1496 + index: Int, 1497 + out: List(Type), 1498 + ) -> List(Type) { 1499 + case dict.get(labelled_fields, index), unlabelled_fields { 1500 + Error(_), [] -> list.reverse(out) 1501 + Ok(value), _ -> 1502 + reorder_arguments(unlabelled_fields, labelled_fields, index + 1, [ 1503 + value, 1504 + ..out 1505 + ]) 1506 + Error(_), [first, ..rest] -> 1507 + reorder_arguments(rest, labelled_fields, index + 1, [first, ..out]) 1508 + } 1509 + } 1510 + 1511 + fn split_arguments( 1512 + arguments: List(CompiledArgument), 1513 + field_map: FieldMap, 1514 + labelled: Dict(Int, Type), 1515 + unlabelled: List(Type), 1516 + seen_labels: Set(String), 1517 + ) -> Result(#(List(Type), Dict(Int, Type)), Error) { 1518 + case arguments { 1519 + [] -> Ok(#(list.reverse(unlabelled), labelled)) 1520 + [first, ..rest] -> 1521 + case first.label { 1522 + None -> 1523 + split_arguments( 1524 + rest, 1525 + field_map, 1526 + labelled, 1527 + [first.value.type_, ..unlabelled], 1528 + seen_labels, 1529 + ) 1530 + Some(label) -> 1531 + case set.contains(seen_labels, label) { 1532 + True -> Error(DuplicateLabel(label:)) 1533 + False -> 1534 + case dict.get(field_map.fields, label) { 1535 + Error(_) -> 1536 + Error(UnknownLabel( 1537 + label:, 1538 + available_labels: dict.keys(field_map.fields), 1539 + )) 1540 + Ok(index) -> 1541 + split_arguments( 1542 + rest, 1543 + field_map, 1544 + dict.insert(labelled, index, first.value.type_), 1545 + unlabelled, 1546 + set.insert(seen_labels, label), 1547 + ) 1548 + } 1549 + } 1550 + } 1551 + } 1552 + } 1553 + 1554 + fn assert_no_labelled_arguments( 1555 + arguments: List(CompiledArgument), 1556 + ) -> fn(State) -> Result(#(State, List(Type)), Error) { 1557 + arguments 1558 + |> list.try_map(fn(argument) { 1559 + case argument.label { 1560 + None -> Ok(argument.value.type_) 1561 + Some(label) -> Error(UnexpectedLabelledArgument(label:)) 1562 + } 1563 + }) 1564 + |> pure 1565 + }
+154 -1
test/trick_test.gleam
··· 915 915 ) 916 916 use function <- trick.parameter( 917 917 "function", 918 - trick.Function([type_int, type_int], type_float), 918 + trick.Function([type_int, type_int], type_float, field_map: None), 919 919 ) 920 920 use generic <- trick.parameter("generic", trick.TypeVariable(82)) 921 921 [tuple, function, generic] ··· 980 980 |> trick.block 981 981 |> trick.expression_to_string 982 982 } 983 + 984 + pub fn labelled_call_test() { 985 + { 986 + use wibble <- trick.function("wibble", { 987 + use left <- trick.labelled_parameter("left", "left", type_int) 988 + use right <- trick.labelled_parameter("right", "right", type_int) 989 + use condition <- trick.labelled_parameter( 990 + "condition", 991 + "condition", 992 + type_bool, 993 + ) 994 + left 995 + |> trick.equal(right) 996 + |> trick.and(condition) 997 + |> trick.expression 998 + |> trick.function_body 999 + }) 1000 + 1001 + use _main <- trick.function( 1002 + "main", 1003 + trick.function_body( 1004 + trick.expression( 1005 + trick.labelled_call(wibble, [ 1006 + trick.argument(None, trick.bool(True)), 1007 + trick.argument(Some("right"), trick.int(2)), 1008 + trick.argument(Some("left"), trick.int(1)), 1009 + ]), 1010 + ), 1011 + ), 1012 + ) 1013 + 1014 + trick.empty() 1015 + } 1016 + |> trick.to_string 1017 + |> unwrap 1018 + |> birdie.snap("labelled_call") 1019 + } 1020 + 1021 + pub fn labels_affect_ordering_test() { 1022 + let assert Error(error) = 1023 + trick.to_string({ 1024 + use wibble <- trick.function("wibble", { 1025 + use first <- trick.labelled_parameter("first", "first", type_int) 1026 + use second <- trick.labelled_parameter("second", "second", type_float) 1027 + use third <- trick.labelled_parameter("third", "third", type_bool) 1028 + [first, second, third] 1029 + |> trick.tuple 1030 + |> trick.expression 1031 + |> trick.function_body 1032 + }) 1033 + 1034 + use _main <- trick.function( 1035 + "main", 1036 + trick.function_body( 1037 + trick.expression( 1038 + trick.labelled_call(wibble, [ 1039 + trick.argument(Some("second"), trick.int(1)), 1040 + trick.argument(Some("third"), trick.float(1.0)), 1041 + trick.argument(Some("first"), trick.bool(True)), 1042 + ]), 1043 + ), 1044 + ), 1045 + ) 1046 + 1047 + trick.empty() 1048 + }) 1049 + 1050 + assert error == trick.TypeMismatch(expected: type_int, got: type_bool) 1051 + } 1052 + 1053 + pub fn incorrect_arity_labelled_call_test() { 1054 + let assert Error(error) = 1055 + trick.to_string({ 1056 + use wibble <- trick.function("wibble", { 1057 + use first <- trick.labelled_parameter("first", "first", type_int) 1058 + use second <- trick.labelled_parameter("second", "second", type_float) 1059 + use third <- trick.labelled_parameter("third", "third", type_bool) 1060 + [first, second, third] 1061 + |> trick.tuple 1062 + |> trick.expression 1063 + |> trick.function_body 1064 + }) 1065 + 1066 + use _main <- trick.function( 1067 + "main", 1068 + trick.function_body( 1069 + trick.expression( 1070 + trick.labelled_call(wibble, [ 1071 + trick.argument(Some("second"), trick.float(1.0)), 1072 + trick.argument(Some("third"), trick.bool(True)), 1073 + ]), 1074 + ), 1075 + ), 1076 + ) 1077 + 1078 + trick.empty() 1079 + }) 1080 + 1081 + assert error == trick.IncorrectNumberOfArguments(expected: 3, got: 2) 1082 + } 1083 + 1084 + pub fn duplicate_label_in_call_test() { 1085 + let assert Error(error) = 1086 + trick.to_string({ 1087 + use wibble <- trick.function("wibble", { 1088 + use first <- trick.labelled_parameter("first", "first", type_int) 1089 + use second <- trick.labelled_parameter("second", "second", type_float) 1090 + use third <- trick.labelled_parameter("third", "third", type_bool) 1091 + [first, second, third] 1092 + |> trick.tuple 1093 + |> trick.expression 1094 + |> trick.function_body 1095 + }) 1096 + 1097 + use _main <- trick.function( 1098 + "main", 1099 + trick.function_body( 1100 + trick.expression( 1101 + trick.labelled_call(wibble, [ 1102 + trick.argument(Some("second"), trick.int(1)), 1103 + trick.argument(Some("second"), trick.float(1.0)), 1104 + trick.argument(Some("third"), trick.bool(True)), 1105 + ]), 1106 + ), 1107 + ), 1108 + ) 1109 + 1110 + trick.empty() 1111 + }) 1112 + 1113 + assert error == trick.DuplicateLabel("second") 1114 + } 1115 + 1116 + pub fn duplicate_label_in_definition_test() { 1117 + let assert Error(error) = 1118 + trick.to_string( 1119 + trick.function( 1120 + "wibble", 1121 + { 1122 + use first <- trick.labelled_parameter("first", "first", type_int) 1123 + use second <- trick.labelled_parameter("first", "second", type_float) 1124 + use third <- trick.labelled_parameter("third", "third", type_bool) 1125 + [first, second, third] 1126 + |> trick.tuple 1127 + |> trick.expression 1128 + |> trick.function_body 1129 + }, 1130 + fn(_) { trick.empty() }, 1131 + ), 1132 + ) 1133 + 1134 + assert error == trick.DuplicateLabel("first") 1135 + }