| ... | ... | @@ -1,10 +1,13 @@ |
| 1 | | //! Semantic Analysis |
| 1 | //! Semantic Analysis converts `Teg` |
| 2 | 2 | //! |
| 3 | | //! Designed as a nonrecursive pause/resumable state machine, Sema converts |
| 4 | | //! declarations to Intern Pool `Value`s and function bodies to `Air`. |
| 3 | //! Designed as a nonrecursive state machine which can be paused when an |
| 4 | //! unfinished dependency is encountered, allowing the thread to work |
| 5 | //! on other analysis jobs. |
| 5 | 6 | //! |
| 6 | | //! - Compile time resolution of values |
| 7 | | //! - Type checking |
| 7 | //! Sema's responsibilities include |
| 8 | //! - Operation validation and reporting of most compile errors. |
| 9 | //! - Evaluation of `comptime` code. |
| 10 | //! - Declaring dependencies on other analysis units. |
| 8 | 11 | |
| 9 | 12 | comp: *Comp, |
| 10 | 13 | ip: *InternPool, |
| ... | ... | @@ -175,6 +178,7 @@ const Frame = struct { |
| 175 | 178 | |
| 176 | 179 | undefined: void, |
| 177 | 180 | conditional: Conditional, |
| 181 | func: Func, |
| 178 | 182 | }; |
| 179 | 183 | |
| 180 | 184 | const Conditional = enum { |
| ... | ... | @@ -183,7 +187,16 @@ const Frame = struct { |
| 183 | 187 | runtime_no, |
| 184 | 188 | }; |
| 185 | 189 | |
| 190 | const Func = union(enum) { |
| 191 | return_type, |
| 192 | argument: u16, |
| 193 | }; |
| 194 | |
| 186 | 195 | fn initFromBlock(s: *Sema, block_index: Teg.Inst.Index, mode: AnalysisMode, data: Data) Frame { |
| 196 | switch (s.teg.instTag(block_index)) { |
| 197 | else => unreachable, // not a block |
| 198 | .block, .@"comptime" => {}, |
| 199 | } |
| 187 | 200 | const list = s.teg.instEncodedData(block_index) |
| 188 | 201 | .decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index)) |
| 189 | 202 | .extra_index.get(&s.teg) |
| ... | ... | @@ -476,7 +489,8 @@ const InstructionKind = enum { |
| 476 | 489 | .@"comptime", |
| 477 | 490 | .@"if", |
| 478 | 491 | => .control_flow, |
| 479 | | // these pop frames, which may end the compilation |
| 492 | // break is used to pop a frame, which has the |
| 493 | // possibility to end analysis |
| 480 | 494 | .@"break", |
| 481 | 495 | => .pop_control_flow, |
| 482 | 496 | // these are statements which do not evaluate to anything (void) |
| ... | ... | @@ -501,6 +515,12 @@ const PopControlFlowState = enum { stop, @"continue" }; |
| 501 | 515 | |
| 502 | 516 | fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() { |
| 503 | 517 | switch (tag) { |
| 518 | .block => unreachable, // frame must already be pushed |
| 519 | .declaration, |
| 520 | .variable_declaration, |
| 521 | .test_declaration, |
| 522 | => unreachable, // declarations are not analzed by Sema |
| 523 | |
| 504 | 524 | // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function. |
| 505 | 525 | // this way we can avoid repeating the non-trivial call into InternPool |
| 506 | 526 | .param => { |
| ... | ... | @@ -666,6 +686,8 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 666 | 686 | return try s.fieldAccessArray(lhs, lhs_type, field.rhs); |
| 667 | 687 | } else if (lhs_type.toStructType(s.ip)) |struct_type| { |
| 668 | 688 | return try s.fieldAccessStruct(lhs, lhs_type, struct_type, field.rhs); |
| 689 | } else if (lhs_type.toTupleType(s.ip)) |tuple_type| { |
| 690 | return try s.fieldAccessTuple(lhs, lhs_type, tuple_type, field.rhs); |
| 669 | 691 | } else if (lhs.value()) |value| { |
| 670 | 692 | if (value.valueTag(s.ip) == .external_value) { |
| 671 | 693 | const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field.rhs, .string); |
| ... | ... | @@ -877,8 +899,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 877 | 899 | // return Value.void.toAir(); |
| 878 | 900 | // }, |
| 879 | 901 | |
| 880 | | // control flow |
| 881 | | .block => unreachable, // frame should already be pushed |
| 882 | 902 | .@"comptime" => { |
| 883 | 903 | const index = s.frame.currentIndex(); |
| 884 | 904 | try s.pushFrame(); |
| ... | ... | @@ -892,50 +912,66 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 892 | 912 | .{ .params = Type, .captures = Air.Inst.Ref }, |
| 893 | 913 | .{ data.params.len, data.captures.len }, |
| 894 | 914 | ); |
| 895 | | defer reserve.deinit(); |
| 896 | | if (data.captures.len > 0) @panic("captures"); |
| 915 | if (data.captures.len > 0) @panic("TODO: captures"); |
| 916 | |
| 917 | var handled_reserve: std.debug.SafetyLock = .{}; |
| 918 | handled_reserve.lock(); |
| 919 | defer handled_reserve.assertUnlocked(); |
| 920 | errdefer handled_reserve.unlock(); |
| 897 | 921 | |
| 898 | 922 | for (data.params.slice(&s.teg), 0..) |param, i| { |
| 899 | 923 | const src = s.lazyNode(.argument(@intCast(i))); |
| 900 | | const ty = try s.getType(src, param.unwrap() orelse @panic("TODO: inferred param sets")); |
| 901 | | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 902 | | return s.fail(.param_comptime_only, .{ |
| 903 | | .src = src, |
| 904 | | .type = ty, |
| 905 | | }); |
| 924 | |
| 925 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); |
| 926 | switch (known.decode()) { |
| 927 | .value => |val| { |
| 928 | const ty = (try s.coerceValue(src, val, .type)).toTypeUnsafe(s.ip); |
| 929 | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 930 | return s.fail(.param_comptime_only, .{ |
| 931 | .src = src, |
| 932 | .type = ty, |
| 933 | }); |
| 934 | } |
| 935 | reserve.items(.params)[i] = ty; |
| 936 | }, |
| 937 | .index => |block| { |
| 938 | const mode = s.frame.mode; |
| 939 | try s.pushFrame(); |
| 940 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } }); |
| 941 | s.frame.mark_index = reserve.index(); |
| 942 | handled_reserve.unlock(); |
| 943 | return; |
| 944 | }, |
| 906 | 945 | } |
| 907 | | reserve.items(.params)[i] = ty; |
| 908 | 946 | } |
| 909 | 947 | |
| 910 | | const return_type = try s.getType( |
| 911 | | s.lazyNode(.return_type), |
| 912 | | data.return_type.unwrap() orelse .void, |
| 913 | | ); |
| 914 | | |
| 915 | | const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex()); |
| 916 | | const func_type = try s.makeType(.{ |
| 917 | | .function_type = .{ |
| 918 | | .param_types = reserve.items(.params), |
| 919 | | .return_type = return_type, |
| 920 | | }, |
| 921 | | }); |
| 922 | | const func_value = try s.makeValue(.{ |
| 923 | | .func = .{ |
| 924 | | .ty = func_type, |
| 925 | | .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one! |
| 926 | | .teg_body_inst = tracked_inst, |
| 927 | | }, |
| 928 | | }); |
| 929 | | |
| 930 | | s.inst_map.add(s.frame.currentIndex(), func_value.toAir()); |
| 931 | | |
| 932 | | s.frame.advance(); |
| 948 | const return_type = ret: { |
| 949 | switch ((data.return_type.unwrap() orelse break :ret null).decode()) { |
| 950 | .value => |val| { |
| 951 | const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip); |
| 952 | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 953 | return s.fail(.runtime_incompatible_type, .{ |
| 954 | .src = s.lazyNode(.return_type), |
| 955 | .type = ty, |
| 956 | }); |
| 957 | } |
| 958 | break :ret ty; |
| 959 | }, |
| 960 | .index => |block| { |
| 961 | const mode = s.frame.mode; |
| 962 | try s.pushFrame(); |
| 963 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type }); |
| 964 | s.frame.mark_index = reserve.index(); |
| 965 | handled_reserve.unlock(); |
| 966 | return; |
| 967 | }, |
| 968 | } |
| 969 | }; |
| 933 | 970 | |
| 934 | | // TODO: move this queue once we perform one of these |
| 935 | | // - depend on the return type of an inferred-return-type function |
| 936 | | // - use the value in any runtime context () |
| 937 | | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); |
| 971 | try s.postVisitFunctionPrototype(reserve, return_type); |
| 972 | handled_reserve.unlock(); |
| 938 | 973 | }, |
| 974 | |
| 939 | 975 | .@"break" => { |
| 940 | 976 | const brk: Teg.Break = s.frame.currentPayload(.@"break"); |
| 941 | 977 | const operand = s.getRef(brk.operand, .marked); |
| ... | ... | @@ -978,12 +1014,14 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 978 | 1014 | |
| 979 | 1015 | switch (data.conditional) { |
| 980 | 1016 | .comptime_condition => { |
| 1017 | assert(operand.kind == .value); // must be comptime |
| 981 | 1018 | s.inst_map.add(s.frame.currentIndex(), operand); |
| 982 | 1019 | s.frame.advance(); |
| 983 | 1020 | return .@"continue"; |
| 984 | 1021 | }, |
| 985 | 1022 | .runtime_yes => { |
| 986 | 1023 | try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark); |
| 1024 | mark.deinit(); |
| 987 | 1025 | |
| 988 | 1026 | switch (if_data.no.decode()) { |
| 989 | 1027 | .value => { |
| ... | ... | @@ -1025,6 +1063,79 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1025 | 1063 | })); |
| 1026 | 1064 | s.frame.advance(); |
| 1027 | 1065 | }, |
| 1066 | .func => { |
| 1067 | const func_data: Teg.Func = s.frame.currentPayload(.func); |
| 1068 | |
| 1069 | const reserve: FunctionReserve = .fromIndex( |
| 1070 | &s.scratch, |
| 1071 | mark.index, |
| 1072 | .{ func_data.params.len, func_data.captures.len }, |
| 1073 | ); |
| 1074 | |
| 1075 | const return_type = switch (data.func) { |
| 1076 | .argument => |arg_num| ret: { |
| 1077 | for ( |
| 1078 | func_data.params.slice(&s.teg)[arg_num + 1 ..], |
| 1079 | reserve.items(.params)[arg_num + 1 ..], |
| 1080 | arg_num + 1.., |
| 1081 | ) |param, *dst, i| { |
| 1082 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); |
| 1083 | switch (known.decode()) { |
| 1084 | .value => |val| { |
| 1085 | const ty = (try s.coerceValue(s.lazyNode(.argument(@intCast(i))), val, .type)).toTypeUnsafe(s.ip); |
| 1086 | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 1087 | return s.fail(.param_comptime_only, .{ |
| 1088 | .src = s.lazyNode(.argument(@intCast(i))), |
| 1089 | .type = ty, |
| 1090 | }); |
| 1091 | } |
| 1092 | dst.* = ty; |
| 1093 | }, |
| 1094 | .index => |block| { |
| 1095 | const mode = s.frame.mode; |
| 1096 | try s.pushFrame(); |
| 1097 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } }); |
| 1098 | return .@"continue"; |
| 1099 | }, |
| 1100 | } |
| 1101 | } |
| 1102 | |
| 1103 | switch ((func_data.return_type.unwrap() orelse break :ret null).decode()) { |
| 1104 | .value => |val| { |
| 1105 | const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip); |
| 1106 | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 1107 | return s.fail(.runtime_incompatible_type, .{ |
| 1108 | .src = s.lazyNode(.return_type), |
| 1109 | .type = ty, |
| 1110 | }); |
| 1111 | } |
| 1112 | break :ret ty; |
| 1113 | }, |
| 1114 | .index => |block| { |
| 1115 | const mode = s.frame.mode; |
| 1116 | try s.pushFrame(); |
| 1117 | s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type }); |
| 1118 | s.frame.mark_index = reserve.index(); |
| 1119 | return .@"continue"; |
| 1120 | }, |
| 1121 | } |
| 1122 | }, |
| 1123 | .return_type => ret: { |
| 1124 | const val = operand.value() orelse unreachable; // must be comptime |
| 1125 | const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip); |
| 1126 | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 1127 | return s.fail(.runtime_incompatible_type, .{ |
| 1128 | .src = s.lazyNode(.return_type), |
| 1129 | .type = ty, |
| 1130 | }); |
| 1131 | } |
| 1132 | break :ret ty; |
| 1133 | }, |
| 1134 | }; |
| 1135 | |
| 1136 | try s.postVisitFunctionPrototype(reserve, return_type); |
| 1137 | return .@"continue"; |
| 1138 | }, |
| 1028 | 1139 | else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}), |
| 1029 | 1140 | } |
| 1030 | 1141 | |
| ... | ... | @@ -1141,7 +1252,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1141 | 1252 | var has_errors = false; |
| 1142 | 1253 | var is_constant = true; |
| 1143 | 1254 | |
| 1144 | | // Allocate scratch space to store the values as they are being prepared. |
| 1145 | 1255 | const scratch = try s.scratch.reserve( |
| 1146 | 1256 | s.arena(), |
| 1147 | 1257 | Air.Inst.Ref.Optional, |
| ... | ... | @@ -1150,11 +1260,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1150 | 1260 | defer scratch.deinit(); |
| 1151 | 1261 | @memset(scratch.slice(), .none); |
| 1152 | 1262 | |
| 1263 | // The ordering of `storage` is defined by the struct's type, which |
| 1264 | // may be different than the instantiation. The fields are visited in |
| 1265 | // order of the instantiation. |
| 1153 | 1266 | for (node.keys(&s.teg), node.inits(&s.teg), 0..) |init_key, init_value, i| { |
| 1154 | | // The order of `storage` is as ordered by the type, but this |
| 1155 | | // can be in a different ordering than the initialization. We |
| 1156 | | // guarantee the ordering the initializers are visited is in the |
| 1157 | | // order they are declared. |
| 1158 | 1267 | const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| { |
| 1159 | 1268 | if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip)) |
| 1160 | 1269 | break :index j; |
| ... | ... | @@ -1181,7 +1290,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1181 | 1290 | .marked, |
| 1182 | 1291 | ) catch |e| switch (e) { |
| 1183 | 1292 | error.AnalysisFail => { |
| 1184 | | // Instead of bailing out with `try`, we will continue the loop. |
| 1293 | // Instead of bailing out with `try`, all fields will be analyzed |
| 1185 | 1294 | has_errors = true; |
| 1186 | 1295 | // Mark something non-null so that duplicate field errors are emitted. |
| 1187 | 1296 | scratch.at(found_index).* = comptime Value.void.toAir().toOptional(); |
| ... | ... | @@ -1193,29 +1302,29 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1193 | 1302 | scratch.at(found_index).* = coerced.toOptional(); |
| 1194 | 1303 | } |
| 1195 | 1304 | |
| 1196 | | // Check for missing fields |
| 1197 | | { |
| 1198 | | const storage = scratch.slice(); |
| 1199 | | var i: usize = 0; |
| 1200 | | while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { |
| 1201 | | try s.addError(.struct_init_missing_field, .{ |
| 1202 | | .ty = ty, |
| 1203 | | .field = @intCast(index), |
| 1204 | | .src = s.lazyNode(.main_node), |
| 1205 | | }); |
| 1206 | | i = index + 1; |
| 1207 | | } |
| 1208 | | if (i != 0) has_errors = true; |
| 1305 | const storage = scratch.slice(); |
| 1306 | var i: usize = 0; |
| 1307 | while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { |
| 1308 | try s.addError(.struct_init_missing_field, .{ |
| 1309 | .ty = ty, |
| 1310 | .field = @intCast(index), |
| 1311 | .src = s.lazyNode(.main_node), |
| 1312 | }); |
| 1313 | i = index + 1; |
| 1209 | 1314 | } |
| 1315 | if (i != 0) has_errors = true; |
| 1210 | 1316 | |
| 1211 | 1317 | if (has_errors) |
| 1212 | 1318 | return Error.AnalysisFail; |
| 1213 | 1319 | |
| 1320 | // Now that there are definitely no missing fields, it is safe to |
| 1321 | // interpret `scratch` as `Air.Inst.Ref` |
| 1322 | |
| 1214 | 1323 | if (is_constant) { |
| 1215 | | // Convert all items in storage into a Value type |
| 1216 | 1324 | const result_storage = scratch.sliceReinterpret(Value); |
| 1217 | | for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { |
| 1218 | | value.* = ref.unwrap().?.decode().value; |
| 1325 | for (scratch.slice(), result_storage) |ref, *value| { |
| 1326 | // `ref` and `value` alias; this coercion is performed in-place |
| 1327 | value.* = (ref.unwrap() orelse unreachable).decode().value; |
| 1219 | 1328 | } |
| 1220 | 1329 | return try s.makeValueAir(.{ .struct_value = .{ |
| 1221 | 1330 | .ty = ty, |
| ... | ... | @@ -1224,7 +1333,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1224 | 1333 | } else { |
| 1225 | 1334 | try s.ensureAtRuntime(s.lazyNode(.main_node)); |
| 1226 | 1335 | |
| 1227 | | // Create a runtime struct instantiation |
| 1228 | 1336 | const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); |
| 1229 | 1337 | return try s.addRef(.struct_init, .{ |
| 1230 | 1338 | .ty = ty, |
| ... | ... | @@ -1239,6 +1347,73 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) |
| 1239 | 1347 | return s.makeValueAir(.{ .array_type = .{ .item_type = operand } }); |
| 1240 | 1348 | }, |
| 1241 | 1349 | |
| 1350 | .tuple_type => { |
| 1351 | const data: Teg.TupleType = s.frame.currentPayload(.tuple_type); |
| 1352 | |
| 1353 | const scratch = try s.scratch.reserve(s.arena(), Type, data.items.len); |
| 1354 | defer scratch.deinit(); |
| 1355 | |
| 1356 | for (data.items.slice(&s.teg), scratch.slice(), 0..) |src, *dst, i| { |
| 1357 | dst.* = try s.getType(s.lazyNode(.argument(@intCast(i))), src); |
| 1358 | } |
| 1359 | |
| 1360 | return s.makeValueAir(.{ .tuple_type = .{ .items = scratch.slice() } }); |
| 1361 | }, |
| 1362 | |
| 1363 | .tuple_init => { |
| 1364 | const data: Teg.TupleInit = s.frame.currentPayload(.tuple_init); |
| 1365 | |
| 1366 | _ = data.type_hint; // TODO: use type hint |
| 1367 | |
| 1368 | const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, data.items.len); |
| 1369 | defer scratch.deinit(); |
| 1370 | |
| 1371 | var is_const = true; |
| 1372 | for (data.items.slice(&s.teg), scratch.slice()) |src, *dst| { |
| 1373 | const result = s.getRef(src, .marked); |
| 1374 | dst.* = result; |
| 1375 | if (result.kind == .index) is_const = false; |
| 1376 | } |
| 1377 | |
| 1378 | if (!is_const) { |
| 1379 | try s.ensureAtRuntime(s.lazyNode(.main_node)); |
| 1380 | |
| 1381 | for (scratch.slice(), 0..) |*ref, i| { |
| 1382 | ref.* = try s.ensureValueRuntime(s.lazyNode(.argument(@intCast(i))), ref.*); |
| 1383 | } |
| 1384 | } |
| 1385 | |
| 1386 | const ty = ty: { |
| 1387 | const type_scratch = try s.scratch.reserve(s.arena(), Type, data.items.len); |
| 1388 | defer type_scratch.deinit(); |
| 1389 | |
| 1390 | for (scratch.slice(), type_scratch.slice()) |val, *ty| { |
| 1391 | ty.* = s.typeOfRef(val); |
| 1392 | } |
| 1393 | |
| 1394 | break :ty try s.makeType(.{ .tuple_type = .{ .items = type_scratch.slice() } }); |
| 1395 | }; |
| 1396 | |
| 1397 | if (is_const) { |
| 1398 | const result_storage = scratch.sliceReinterpret(Value); |
| 1399 | for (scratch.slice(), result_storage) |ref, *value| { |
| 1400 | // `ref` and `value` alias; this coercion is performed in-place |
| 1401 | value.* = ref.decode().value; |
| 1402 | } |
| 1403 | |
| 1404 | return try s.makeValueAir(.{ .tuple_value = .{ |
| 1405 | .items = result_storage, |
| 1406 | .type = ty, |
| 1407 | } }); |
| 1408 | } else { |
| 1409 | const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); |
| 1410 | return try s.addRef(.struct_init, .{ |
| 1411 | .field_values = value_index, |
| 1412 | .ty = ty, |
| 1413 | }); |
| 1414 | } |
| 1415 | }, |
| 1416 | |
| 1242 | 1417 | .result_type_argument => { |
| 1243 | 1418 | const result: Teg.ResultTypeArgument = s.frame.currentData(.result_type_argument); |
| 1244 | 1419 | |
| ... | ... | @@ -1605,6 +1780,11 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value { |
| 1605 | 1780 | return s.coerceArrayValue(src, value, to_array_type.item_type); |
| 1606 | 1781 | } |
| 1607 | 1782 | } |
| 1783 | if (to.toTupleType(s.ip)) |to_tuple_type| { |
| 1784 | if (value.valueTag(s.ip) == .tuple_value) { |
| 1785 | return s.coerceTupleValue(src, value, from, to, to_tuple_type); |
| 1786 | } |
| 1787 | } |
| 1608 | 1788 | }, |
| 1609 | 1789 | } |
| 1610 | 1790 | return s.coercionError(src, from, to); |
| ... | ... | @@ -1729,6 +1909,44 @@ fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Val |
| 1729 | 1909 | } }); |
| 1730 | 1910 | } |
| 1731 | 1911 | |
| 1912 | fn coerceTupleValue( |
| 1913 | s: *Sema, |
| 1914 | src: LazyNode, |
| 1915 | value: Value, |
| 1916 | value_type: Type, |
| 1917 | target_type: Type, |
| 1918 | target_key: InternPool.Key.TupleType, |
| 1919 | ) !Value { |
| 1920 | const tuple_value = value.toKey(s.ip).tuple_value; |
| 1921 | if (tuple_value.items.len != target_key.items.len) |
| 1922 | return s.coercionError(src, value_type, target_type); |
| 1923 | |
| 1924 | const len = tuple_value.items.len; |
| 1925 | |
| 1926 | const scratch = try s.scratch.reserveMulti( |
| 1927 | s.arena(), |
| 1928 | .{ .values = Value, .types = Type }, |
| 1929 | .{ @intCast(len), @intCast(len) }, |
| 1930 | ); |
| 1931 | defer scratch.deinit(); |
| 1932 | |
| 1933 | // This function will append new items to the intern pool, invalidating |
| 1934 | // existing ones. Copy all data into a stable slice first. |
| 1935 | @memcpy(scratch.items(.values), tuple_value.items); |
| 1936 | @memcpy(scratch.items(.types), target_key.items); |
| 1937 | |
| 1938 | for (0..len) |i| { |
| 1939 | // `s.scratch` can be invalidated by having a tuple of tuples and coercing it. |
| 1940 | const coerced = try s.coerceValue(src, scratch.at(.values, i).*, scratch.at(.types, i).*); |
| 1941 | scratch.at(.values, i).* = coerced; |
| 1942 | } |
| 1943 | |
| 1944 | return try s.makeValue(.{ .tuple_value = .{ |
| 1945 | .type = target_type, |
| 1946 | .items = scratch.items(.values), |
| 1947 | } }); |
| 1948 | } |
| 1949 | |
| 1732 | 1950 | fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex { |
| 1733 | 1951 | var nsi = s.ns_index; |
| 1734 | 1952 | while (true) { |
| ... | ... | @@ -1862,6 +2080,68 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.In |
| 1862 | 2080 | } |
| 1863 | 2081 | } |
| 1864 | 2082 | |
| 2083 | fn fieldAccessTuple( |
| 2084 | s: *Sema, |
| 2085 | lhs: Air.Inst.Ref, |
| 2086 | lhs_type: Type, |
| 2087 | tuple_type: InternPool.Key.TupleType, |
| 2088 | field: Teg.Inst.Ref, |
| 2089 | ) !Air.Inst.Ref { |
| 2090 | const index_value = try s.getCoercedValue(s.lazyNode(.rhs), field, .int32); |
| 2091 | const index_signed = index_value.toInt32(s.ip) orelse unreachable; |
| 2092 | if (index_signed < 0 or index_signed >= tuple_type.items.len) { |
| 2093 | return s.fail(.field_lookup_failed, .{ |
| 2094 | .ty = lhs_type, |
| 2095 | .src = s.lazyNode(.lhs), |
| 2096 | .field = index_value, |
| 2097 | }); |
| 2098 | } |
| 2099 | const index: u32 = @intCast(index_signed); |
| 2100 | |
| 2101 | switch (lhs.decode()) { |
| 2102 | .value => |value| { |
| 2103 | return value.toKey(s.ip).tuple_value.items[index].toAir(); |
| 2104 | }, |
| 2105 | .index => |lhs_index| { |
| 2106 | try s.ensureAtRuntime(s.lazyNode(.lhs)); |
| 2107 | return s.addRef(.field_tuple, .{ |
| 2108 | .base = lhs_index, |
| 2109 | .field_index = index, |
| 2110 | }); |
| 2111 | }, |
| 2112 | } |
| 2113 | } |
| 2114 | |
| 2115 | const FunctionReserve = core.U32Stack.ReserveMulti(.{ .params = Type, .captures = Air.Inst.Ref }); |
| 2116 | |
| 2117 | fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ?Type) !void { |
| 2118 | const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex()); |
| 2119 | const func_type = try s.makeType(.{ |
| 2120 | .function_type = .{ |
| 2121 | .param_types = reserve.items(.params), |
| 2122 | .return_type = return_type, |
| 2123 | }, |
| 2124 | }); |
| 2125 | const func_value = try s.makeValue(.{ |
| 2126 | .func = .{ |
| 2127 | .ty = func_type, |
| 2128 | .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one! |
| 2129 | .teg_body_inst = tracked_inst, |
| 2130 | }, |
| 2131 | }); |
| 2132 | |
| 2133 | s.inst_map.add(s.frame.currentIndex(), func_value.toAir()); |
| 2134 | |
| 2135 | s.frame.advance(); |
| 2136 | |
| 2137 | reserve.deinit(); |
| 2138 | |
| 2139 | // TODO: move this queue once we perform one of these |
| 2140 | // - depend on the return type of an inferred-return-type function |
| 2141 | // - use the value in any runtime context () |
| 2142 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); |
| 2143 | } |
| 2144 | |
| 1865 | 2145 | // |
| 1866 | 2146 | |
| 1867 | 2147 | fn tempAir(s: *const Sema) Air { |