Commit 0ec6b2dd88

mlugg <mlugg@mlugg.co.uk>
2025-01-20 00:19:59
compiler: simplify generic functions, fix issues with inline calls
The original motivation here was to fix regressions caused by #22414. However, while working on this, I ended up discussing a language simplification with Andrew, which changes things a little from how they worked before #22414. The main user-facing change here is that any reference to a prior function parameter, even if potentially comptime-known at the usage site or even not analyzed, now makes a function generic. This applies even if the parameter being referenced is not a `comptime` parameter, since it could still be populated when performing an inline call. This is a breaking language change. The detection of this is done in AstGen; when evaluating a parameter type or return type, we track whether it referenced any prior parameter, and if so, we mark this type as being "generic" in ZIR. This will cause Sema to not evaluate it until the time of instantiation or inline call. A lovely consequence of this from an implementation perspective is that it eliminates the need for most of the "generic poison" system. In particular, `error.GenericPoison` is now completely unnecessary, because we identify generic expressions earlier in the pipeline; this simplifies the compiler and avoids redundant work. This also entirely eliminates the concept of the "generic poison value". The only remnant of this system is the "generic poison type" (`Type.generic_poison` and `InternPool.Index.generic_poison_type`). This type is used in two places: * During semantic analysis, to represent an unknown result type. * When storing generic function types, to represent a generic parameter/return type. It's possible that these use cases should instead use `.none`, but I leave that investigation to a future adventurer. One last thing. Prior to #22414, inline calls were a little inefficient, because they re-evaluated even non-generic parameter types whenever they were called. Changing this behavior is what ultimately led to #22538. Well, because the new logic will mark a type expression as generic if there is any change its resolved type could differ in an inline call, this redundant work is unnecessary! So, this is another way in which the new design reduces redundant work and complexity. Resolves: #22494 Resolves: #22532 Resolves: #22538
1 parent 216e0f3
lib/std/zig/AstGen.zig
@@ -107,6 +107,8 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
             Zir.Inst.SwitchBlock.Bits,
             Zir.Inst.SwitchBlockErrUnion.Bits,
             Zir.Inst.FuncFancy.Bits,
+            Zir.Inst.Param.Type,
+            Zir.Inst.Func.RetTy,
             => @bitCast(@field(extra, field.name)),
 
             else => @compileError("bad field type"),
@@ -1384,7 +1386,7 @@ fn fnProtoExprInner(
                 const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param;
                 // We pass `prev_param_insts` as `&.{}` here because a function prototype can't refer to previous
                 // arguments (we haven't set up scopes here).
-                const param_inst = try block_scope.addParam(&param_gz, &.{}, tag, name_token, param_name);
+                const param_inst = try block_scope.addParam(&param_gz, &.{}, false, tag, name_token, param_name);
                 assert(param_inst_expected == param_inst);
             }
         }
@@ -1416,6 +1418,7 @@ fn fnProtoExprInner(
 
         .ret_param_refs = &.{},
         .param_insts = &.{},
+        .ret_ty_is_generic = false,
 
         .param_block = block_inst,
         .body_gz = null,
@@ -4336,6 +4339,9 @@ fn fnDeclInner(
     // Note that the capacity here may not be sufficient, as this does not include `anytype` parameters.
     var param_insts: std.ArrayListUnmanaged(Zir.Inst.Index) = try .initCapacity(astgen.arena, fn_proto.ast.params.len);
 
+    // We use this as `is_used_or_discarded` to figure out if parameters / return types are generic.
+    var any_param_used = false;
+
     var noalias_bits: u32 = 0;
     var params_scope = scope;
     const is_var_args = is_var_args: {
@@ -4409,16 +4415,18 @@ fn fnDeclInner(
             } else param: {
                 const param_type_node = param.type_expr;
                 assert(param_type_node != 0);
+                any_param_used = false; // we will check this later
                 var param_gz = decl_gz.makeSubBlock(scope);
                 defer param_gz.unstack();
                 const param_type = try fullBodyExpr(&param_gz, params_scope, coerced_type_ri, param_type_node, .normal);
                 const param_inst_expected: Zir.Inst.Index = @enumFromInt(astgen.instructions.len + 1);
                 _ = try param_gz.addBreakWithSrcNode(.break_inline, param_inst_expected, param_type, param_type_node);
+                const param_type_is_generic = any_param_used;
 
                 const main_tokens = tree.nodes.items(.main_token);
                 const name_token = param.name_token orelse main_tokens[param_type_node];
                 const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param;
-                const param_inst = try decl_gz.addParam(&param_gz, param_insts.items, tag, name_token, param_name);
+                const param_inst = try decl_gz.addParam(&param_gz, param_insts.items, param_type_is_generic, tag, name_token, param_name);
                 assert(param_inst_expected == param_inst);
                 break :param param_inst.toRef();
             };
@@ -4433,6 +4441,7 @@ fn fnDeclInner(
                 .inst = param_inst,
                 .token_src = param.name_token.?,
                 .id_cat = .@"function parameter",
+                .is_used_or_discarded = &any_param_used,
             };
             params_scope = &sub_scope.base;
             try param_insts.append(astgen.arena, param_inst.toIndex().?);
@@ -4446,6 +4455,7 @@ fn fnDeclInner(
 
     var ret_gz = decl_gz.makeSubBlock(params_scope);
     defer ret_gz.unstack();
+    any_param_used = false; // we will check this later
     const ret_ref: Zir.Inst.Ref = inst: {
         // Parameters are in scope for the return type, so we use `params_scope` here.
         // The calling convention will not have parameters in scope, so we'll just use `scope`.
@@ -4459,6 +4469,7 @@ fn fnDeclInner(
         break :inst inst;
     };
     const ret_body_param_refs = try astgen.fetchRemoveRefEntries(param_insts.items);
+    const ret_ty_is_generic = any_param_used;
 
     // We're jumping back in source, so restore the cursor.
     astgen.restoreSourceCursor(saved_cursor);
@@ -4556,6 +4567,7 @@ fn fnDeclInner(
         .ret_ref = ret_ref,
         .ret_gz = &ret_gz,
         .ret_param_refs = ret_body_param_refs,
+        .ret_ty_is_generic = ret_ty_is_generic,
         .lbrace_line = lbrace_line,
         .lbrace_column = lbrace_column,
         .param_block = decl_inst,
@@ -5028,6 +5040,7 @@ fn testDecl(
 
         .ret_param_refs = &.{},
         .param_insts = &.{},
+        .ret_ty_is_generic = false,
 
         .lbrace_line = lbrace_line,
         .lbrace_column = lbrace_column,
@@ -8546,6 +8559,8 @@ fn localVarRef(
                     local_val.used = ident_token;
                 }
 
+                if (local_val.is_used_or_discarded) |ptr| ptr.* = true;
+
                 const value_inst = if (num_namespaces_out != 0) try tunnelThroughClosure(
                     gz,
                     ident,
@@ -11876,6 +11891,7 @@ const Scope = struct {
         /// Track the identifier where it is discarded, like this `_ = foo;`.
         /// 0 means never discarded.
         discarded: Ast.TokenIndex = 0,
+        is_used_or_discarded: ?*bool = null,
         /// String table index.
         name: Zir.NullTerminatedString,
         id_cat: IdCat,
@@ -12223,6 +12239,7 @@ const GenZir = struct {
 
             ret_param_refs: []Zir.Inst.Index,
             param_insts: []Zir.Inst.Index, // refs to params in `body_gz` should still be in `astgen.ref_table`
+            ret_ty_is_generic: bool,
 
             cc_ref: Zir.Inst.Ref,
             ret_ref: Zir.Inst.Ref,
@@ -12322,6 +12339,8 @@ const GenZir = struct {
 
                     .has_cc_body = cc_body.len != 0,
                     .has_ret_ty_body = ret_body.len != 0,
+
+                    .ret_ty_is_generic = args.ret_ty_is_generic,
                 },
             });
 
@@ -12372,7 +12391,10 @@ const GenZir = struct {
 
             const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.Func{
                 .param_block = args.param_block,
-                .ret_body_len = ret_body_len,
+                .ret_ty = .{
+                    .body_len = @intCast(ret_body_len),
+                    .is_generic = args.ret_ty_is_generic,
+                },
                 .body_len = body_len,
             });
             const zir_datas = astgen.instructions.items(.data);
@@ -12535,6 +12557,7 @@ const GenZir = struct {
         /// Previous parameters, which might be referenced in `param_gz` (the new parameter type).
         /// `ref`s of these instructions will be put into this param's type body, and removed from `AstGen.ref_table`.
         prev_param_insts: []const Zir.Inst.Index,
+        ty_is_generic: bool,
         tag: Zir.Inst.Tag,
         /// Absolute token index. This function does the conversion to Decl offset.
         abs_tok_index: Ast.TokenIndex,
@@ -12548,7 +12571,10 @@ const GenZir = struct {
 
         const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Param{
             .name = name,
-            .body_len = @intCast(body_len),
+            .type = .{
+                .body_len = @intCast(body_len),
+                .is_generic = ty_is_generic,
+            },
         });
         gz.astgen.appendBodyWithFixupsExtraRefsArrayList(&gz.astgen.extra, param_body, prev_param_insts);
         param_gz.unstack();
lib/std/zig/Zir.zig
@@ -89,6 +89,8 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {
             Inst.SwitchBlockErrUnion.Bits,
             Inst.FuncFancy.Bits,
             Inst.Declaration.Flags,
+            Inst.Param.Type,
+            Inst.Func.RetTy,
             => @bitCast(code.extra[i]),
 
             else => @compileError("bad field type"),
@@ -2126,7 +2128,7 @@ pub const Inst = struct {
         ref_start_index = static_len,
         _,
 
-        pub const static_len = 71;
+        pub const static_len = 70;
 
         pub fn toRef(i: Index) Inst.Ref {
             return @enumFromInt(@intFromEnum(Index.ref_start_index) + @intFromEnum(i));
@@ -2229,7 +2231,6 @@ pub const Inst = struct {
         bool_true,
         bool_false,
         empty_tuple,
-        generic_poison,
 
         /// This Ref does not correspond to any ZIR instruction or constant
         /// value and may instead be used as a sentinel to indicate null.
@@ -2472,24 +2473,31 @@ pub const Inst = struct {
     };
 
     /// Trailing:
-    /// if (ret_body_len == 1) {
+    /// if (ret_ty.body_len == 1) {
     ///   0. return_type: Ref
     /// }
-    /// if (ret_body_len > 1) {
-    ///   1. return_type: Index // for each ret_body_len
+    /// if (ret_ty.body_len > 1) {
+    ///   1. return_type: Index // for each ret_ty.body_len
     /// }
     /// 2. body: Index // for each body_len
     /// 3. src_locs: SrcLocs // if body_len != 0
     /// 4. proto_hash: std.zig.SrcHash // if body_len != 0; hash of function prototype
     pub const Func = struct {
-        /// If this is 0 it means a void return type.
-        /// If this is 1 it means return_type is a simple Ref
-        ret_body_len: u32,
+        ret_ty: RetTy,
         /// Points to the block that contains the param instructions for this function.
         /// If this is a `declaration`, it refers to the declaration's value body.
         param_block: Index,
         body_len: u32,
 
+        pub const RetTy = packed struct(u32) {
+            /// 0 means `void`.
+            /// 1 means the type is a simple `Ref`.
+            /// Otherwise, the length of a trailing body.
+            body_len: u31,
+            /// Whether the return type is generic, i.e. refers to one or more previous parameters.
+            is_generic: bool,
+        };
+
         pub const SrcLocs = struct {
             /// Line index in the source file relative to the parent decl.
             lbrace_line: u32,
@@ -2539,7 +2547,8 @@ pub const Inst = struct {
             has_ret_ty_ref: bool,
             has_ret_ty_body: bool,
             has_any_noalias: bool,
-            _: u24 = undefined,
+            ret_ty_is_generic: bool,
+            _: u23 = undefined,
         };
     };
 
@@ -3708,8 +3717,14 @@ pub const Inst = struct {
     pub const Param = struct {
         /// Null-terminated string index.
         name: NullTerminatedString,
-        /// The body contains the type of the parameter.
-        body_len: u32,
+        type: Type,
+
+        pub const Type = packed struct(u32) {
+            /// The body contains the type of the parameter.
+            body_len: u31,
+            /// Whether the type is generic, i.e. refers to one or more previous parameters.
+            is_generic: bool,
+        };
     };
 
     /// Trailing:
@@ -4492,7 +4507,7 @@ fn findTrackableInner(
 
             if (extra.data.body_len == 0) {
                 // This is just a prototype. No need to track.
-                assert(extra.data.ret_body_len < 2);
+                assert(extra.data.ret_ty.body_len < 2);
                 return;
             }
 
@@ -4500,11 +4515,11 @@ fn findTrackableInner(
             contents.func_decl = inst;
 
             var extra_index: usize = extra.end;
-            switch (extra.data.ret_body_len) {
+            switch (extra.data.ret_ty.body_len) {
                 0 => {},
                 1 => extra_index += 1,
                 else => {
-                    const body = zir.bodySlice(extra_index, extra.data.ret_body_len);
+                    const body = zir.bodySlice(extra_index, extra.data.ret_ty.body_len);
                     extra_index += body.len;
                     try zir.findTrackableBody(gpa, contents, defers, body);
                 },
@@ -4595,7 +4610,7 @@ fn findTrackableInner(
         .param, .param_comptime => {
             const inst_data = datas[@intFromEnum(inst)].pl_tok;
             const extra = zir.extraData(Inst.Param, inst_data.payload_index);
-            const body = zir.bodySlice(extra.end, extra.data.body_len);
+            const body = zir.bodySlice(extra.end, extra.data.type.body_len);
             try zir.findTrackableBody(gpa, contents, defers, body);
         },
 
@@ -4738,6 +4753,7 @@ pub const FnInfo = struct {
     ret_ty_body: []const Inst.Index,
     body: []const Inst.Index,
     ret_ty_ref: Zir.Inst.Ref,
+    ret_ty_is_generic: bool,
     total_params_len: u32,
     inferred_error_set: bool,
 };
@@ -4779,6 +4795,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
         body: []const Inst.Index,
         ret_ty_ref: Inst.Ref,
         ret_ty_body: []const Inst.Index,
+        ret_ty_is_generic: bool,
         ies: bool,
     } = switch (tags[@intFromEnum(fn_inst)]) {
         .func, .func_inferred => |tag| blk: {
@@ -4789,7 +4806,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
             var ret_ty_ref: Inst.Ref = .none;
             var ret_ty_body: []const Inst.Index = &.{};
 
-            switch (extra.data.ret_body_len) {
+            switch (extra.data.ret_ty.body_len) {
                 0 => {
                     ret_ty_ref = .void_type;
                 },
@@ -4798,7 +4815,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
                     extra_index += 1;
                 },
                 else => {
-                    ret_ty_body = zir.bodySlice(extra_index, extra.data.ret_body_len);
+                    ret_ty_body = zir.bodySlice(extra_index, extra.data.ret_ty.body_len);
                     extra_index += ret_ty_body.len;
                 },
             }
@@ -4811,6 +4828,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
                 .ret_ty_ref = ret_ty_ref,
                 .ret_ty_body = ret_ty_body,
                 .body = body,
+                .ret_ty_is_generic = extra.data.ret_ty.is_generic,
                 .ies = tag == .func_inferred,
             };
         },
@@ -4848,6 +4866,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
                 .ret_ty_ref = ret_ty_ref,
                 .ret_ty_body = ret_ty_body,
                 .body = body,
+                .ret_ty_is_generic = extra.data.bits.ret_ty_is_generic,
                 .ies = extra.data.bits.is_inferred_error,
             };
         },
@@ -4870,6 +4889,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
         .ret_ty_ref = info.ret_ty_ref,
         .body = info.body,
         .total_params_len = total_params_len,
+        .ret_ty_is_generic = info.ret_ty_is_generic,
         .inferred_error_set = info.ies,
     };
 }
@@ -4967,7 +4987,7 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {
                 return null;
             }
             const extra_index = extra.end +
-                extra.data.ret_body_len +
+                extra.data.ret_ty.body_len +
                 extra.data.body_len +
                 @typeInfo(Inst.Func.SrcLocs).@"struct".fields.len;
             return @bitCast([4]u32{
src/Air/types_resolved.zig
@@ -455,9 +455,8 @@ pub fn checkVal(val: Value, zcu: *Zcu) bool {
 
 pub fn checkType(ty: Type, zcu: *Zcu) bool {
     const ip = &zcu.intern_pool;
-    return switch (ty.zigTypeTagOrPoison(zcu) catch |err| switch (err) {
-        error.GenericPoison => return true,
-    }) {
+    if (ty.isGenericPoison()) return true;
+    return switch (ty.zigTypeTag(zcu)) {
         .type,
         .void,
         .bool,
src/arch/wasm/CodeGen.zig
@@ -3170,7 +3170,6 @@ fn lowerConstant(cg: *CodeGen, val: Value, ty: Type) InnerError!WValue {
             .null,
             .empty_tuple,
             .@"unreachable",
-            .generic_poison,
             => unreachable, // non-runtime values
             .false, .true => return .{ .imm32 = switch (simple_value) {
                 .false => 0,
src/codegen/c/Type.zig
@@ -1451,7 +1451,6 @@ pub const Pool = struct {
             .bool_true,
             .bool_false,
             .empty_tuple,
-            .generic_poison,
             .none,
             => unreachable,
 
src/codegen/c.zig
@@ -968,7 +968,6 @@ pub const DeclGen = struct {
                 .null => unreachable,
                 .empty_tuple => unreachable,
                 .@"unreachable" => unreachable,
-                .generic_poison => unreachable,
 
                 .false => try writer.writeAll("false"),
                 .true => try writer.writeAll("true"),
src/codegen/llvm.zig
@@ -3372,7 +3372,6 @@ pub const Object = struct {
             .bool_true,
             .bool_false,
             .empty_tuple,
-            .generic_poison,
             .none,
             => unreachable,
             else => switch (ip.indexToKey(t.toIntern())) {
@@ -3923,7 +3922,6 @@ pub const Object = struct {
                 .null => unreachable, // non-runtime value
                 .empty_tuple => unreachable, // non-runtime value
                 .@"unreachable" => unreachable, // non-runtime value
-                .generic_poison => unreachable, // non-runtime value
 
                 .false => .false,
                 .true => .true,
src/codegen/spirv.zig
@@ -941,7 +941,6 @@ const NavGen = struct {
                     .null,
                     .empty_tuple,
                     .@"unreachable",
-                    .generic_poison,
                     => unreachable, // non-runtime values
 
                     .false, .true => break :cache try self.constBool(val.toBool(), repr),
src/Zcu/PerThread.zig
@@ -627,7 +627,6 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
             // TODO: same as for `ensureComptimeUnitUpToDate` etc
             return error.OutOfMemory;
         },
-        error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         error.ComptimeBreak => unreachable,
     };
@@ -781,7 +780,6 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU
             // for reporting OOM errors without allocating.
             return error.OutOfMemory;
         },
-        error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         error.ComptimeBreak => unreachable,
     };
@@ -967,7 +965,6 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
             // for reporting OOM errors without allocating.
             return error.OutOfMemory;
         },
-        error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         error.ComptimeBreak => unreachable,
     };
@@ -1168,7 +1165,6 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
     };
 
     switch (nav_val.toIntern()) {
-        .generic_poison => unreachable, // assertion failure
         .unreachable_value => unreachable, // assertion failure
         else => {},
     }
@@ -1347,7 +1343,6 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc
             // for reporting OOM errors without allocating.
             return error.OutOfMemory;
         },
-        error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         error.ComptimeBreak => unreachable,
     };
@@ -2665,7 +2660,6 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE
         runtime_param_index += 1;
 
         const opt_opv = sema.typeHasOnePossibleValue(Type.fromInterned(param_ty)) catch |err| switch (err) {
-            error.GenericPoison => unreachable,
             error.ComptimeReturn => unreachable,
             error.ComptimeBreak => unreachable,
             else => |e| return e,
@@ -2698,7 +2692,6 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE
     inner_block.error_return_trace_index = error_return_trace_index;
 
     sema.analyzeFnBody(&inner_block, fn_info.body) catch |err| switch (err) {
-        error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         else => |e| return e,
     };
@@ -2720,7 +2713,6 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE
         !sema.fn_ret_ty.isError(zcu))
     {
         sema.setupErrorReturnTrace(&inner_block, last_arg_index) catch |err| switch (err) {
-            error.GenericPoison => unreachable,
             error.ComptimeReturn => unreachable,
             error.ComptimeBreak => unreachable,
             else => |e| return e,
@@ -2744,7 +2736,6 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE
             .base_node_inst = inner_block.src_base_inst,
             .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0),
         }, ies) catch |err| switch (err) {
-            error.GenericPoison => unreachable,
             error.ComptimeReturn => unreachable,
             error.ComptimeBreak => unreachable,
             else => |e| return e,
@@ -2763,7 +2754,6 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE
     // TODO: this can go away once we fix backends having to resolve `StackTrace`.
     // The codegen timing guarantees that the parameter types will be populated.
     sema.resolveFnTypes(fn_ty, inner_block.nodeOffset(0)) catch |err| switch (err) {
-        error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         error.ComptimeBreak => unreachable,
         else => |e| return e,
src/Air.zig
@@ -1004,7 +1004,6 @@ pub const Inst = struct {
         bool_true = @intFromEnum(InternPool.Index.bool_true),
         bool_false = @intFromEnum(InternPool.Index.bool_false),
         empty_tuple = @intFromEnum(InternPool.Index.empty_tuple),
-        generic_poison = @intFromEnum(InternPool.Index.generic_poison),
 
         /// This Ref does not correspond to any AIR instruction or constant
         /// value and may instead be used as a sentinel to indicate null.
src/codegen.zig
@@ -229,7 +229,6 @@ pub fn generateSymbol(
             .void => unreachable, // non-runtime value
             .null => unreachable, // non-runtime value
             .@"unreachable" => unreachable, // non-runtime value
-            .generic_poison => unreachable, // non-runtime value
             .empty_tuple => return,
             .false, .true => try code.append(gpa, switch (simple_value) {
                 .false => 0,
src/InternPool.zig
@@ -620,11 +620,11 @@ pub const Nav = struct {
         return switch (nav.status) {
             .unresolved => unreachable,
             .type_resolved => |r| {
-                const tag = ip.zigTypeTagOrPoison(r.type) catch unreachable;
+                const tag = ip.zigTypeTag(r.type);
                 return tag == .@"fn";
             },
             .fully_resolved => |r| {
-                const tag = ip.zigTypeTagOrPoison(ip.typeOf(r.val)) catch unreachable;
+                const tag = ip.zigTypeTag(ip.typeOf(r.val));
                 return tag == .@"fn";
             },
         };
@@ -639,13 +639,13 @@ pub const Nav = struct {
             .unresolved => unreachable,
             .type_resolved => |r| {
                 if (r.is_extern_decl) return true;
-                const tag = ip.zigTypeTagOrPoison(r.type) catch unreachable;
+                const tag = ip.zigTypeTag(r.type);
                 if (tag == .@"fn") return true;
                 return false;
             },
             .fully_resolved => |r| {
                 if (ip.indexToKey(r.val) == .@"extern") return true;
-                const tag = ip.zigTypeTagOrPoison(ip.typeOf(r.val)) catch unreachable;
+                const tag = ip.zigTypeTag(ip.typeOf(r.val));
                 if (tag == .@"fn") return true;
                 return false;
             },
@@ -3216,7 +3216,6 @@ pub const Key = union(enum) {
                 .false, .true => .bool_type,
                 .empty_tuple => .empty_tuple_type,
                 .@"unreachable" => .noreturn_type,
-                .generic_poison => .generic_poison_type,
             },
 
             .memoized_call => unreachable,
@@ -4581,6 +4580,10 @@ pub const Index = enum(u32) {
     anyerror_void_error_union_type,
     /// Used for the inferred error set of inline/comptime function calls.
     adhoc_inferred_error_set_type,
+    /// Represents a type which is unknown.
+    /// This is used in functions to represent generic parameter/return types, and
+    /// during semantic analysis to represent unknown result types (i.e. where AstGen
+    /// thought we would have a result type, but we do not).
     generic_poison_type,
     /// `@TypeOf(.{})`; a tuple with zero elements.
     /// This is not the same as `struct {}`, since that is a struct rather than a tuple.
@@ -4617,10 +4620,6 @@ pub const Index = enum(u32) {
     /// `.{}`
     empty_tuple,
 
-    /// Used for generic parameters where the type and value
-    /// is not known until generic function instantiation.
-    generic_poison,
-
     /// Used by Air/Sema only.
     none = std.math.maxInt(u32),
 
@@ -5136,7 +5135,6 @@ pub const static_keys = [_]Key{
     .{ .simple_value = .true },
     .{ .simple_value = .false },
     .{ .simple_value = .empty_tuple },
-    .{ .simple_value = .generic_poison },
 };
 
 /// How many items in the InternPool are statically known.
@@ -6054,8 +6052,6 @@ pub const SimpleValue = enum(u32) {
     true = @intFromEnum(Index.bool_true),
     false = @intFromEnum(Index.bool_false),
     @"unreachable" = @intFromEnum(Index.unreachable_value),
-
-    generic_poison = @intFromEnum(Index.generic_poison),
 };
 
 /// Stored as a power-of-two, with one special value to indicate none.
@@ -11712,7 +11708,6 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index {
         .null_value => .null_type,
         .bool_true, .bool_false => .bool_type,
         .empty_tuple => .empty_tuple_type,
-        .generic_poison => .generic_poison_type,
 
         // This optimization on tags is needed so that indexToKey can call
         // typeOf without being recursive.
@@ -11954,7 +11949,8 @@ pub fn getBackingAddrTag(ip: *const InternPool, val: Index) ?Key.Ptr.BaseAddr.Ta
 
 /// This is a particularly hot function, so we operate directly on encodings
 /// rather than the more straightforward implementation of calling `indexToKey`.
-pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPoison}!std.builtin.TypeId {
+/// Asserts `index` is not `.generic_poison_type`.
+pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId {
     return switch (index) {
         .u0_type,
         .i0_type,
@@ -12017,7 +12013,7 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois
         .anyerror_void_error_union_type => .error_union,
         .empty_tuple_type => .@"struct",
 
-        .generic_poison_type => return error.GenericPoison,
+        .generic_poison_type => unreachable,
 
         // values, not types
         .undef => unreachable,
@@ -12035,7 +12031,6 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois
         .bool_true => unreachable,
         .bool_false => unreachable,
         .empty_tuple => unreachable,
-        .generic_poison => unreachable,
 
         _ => switch (index.unwrap(ip).getTag(ip)) {
             .removed => unreachable,
src/print_value.zig
@@ -84,7 +84,6 @@ pub fn print(
         .simple_value => |simple_value| switch (simple_value) {
             .void => try writer.writeAll("{}"),
             .empty_tuple => try writer.writeAll(".{}"),
-            .generic_poison => try writer.writeAll("(generic poison)"),
             else => try writer.writeAll(@tagName(simple_value)),
         },
         .variable => try writer.writeAll("(variable)"),
src/print_zir.zig
@@ -948,11 +948,13 @@ const Writer = struct {
     fn writeParam(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
         const extra = self.code.extraData(Zir.Inst.Param, inst_data.payload_index);
-        const body = self.code.bodySlice(extra.end, extra.data.body_len);
+        const body = self.code.bodySlice(extra.end, extra.data.type.body_len);
         try stream.print("\"{}\", ", .{
             std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)),
         });
 
+        if (extra.data.type.is_generic) try stream.writeAll("[generic] ");
+
         try self.writeBracedBody(stream, body);
         try stream.writeAll(") ");
         try self.writeSrcTok(stream, inst_data.src_tok);
@@ -2283,7 +2285,7 @@ const Writer = struct {
         var ret_ty_ref: Zir.Inst.Ref = .none;
         var ret_ty_body: []const Zir.Inst.Index = &.{};
 
-        switch (extra.data.ret_body_len) {
+        switch (extra.data.ret_ty.body_len) {
             0 => {
                 ret_ty_ref = .void_type;
             },
@@ -2292,7 +2294,7 @@ const Writer = struct {
                 extra_index += 1;
             },
             else => {
-                ret_ty_body = self.code.bodySlice(extra_index, extra.data.ret_body_len);
+                ret_ty_body = self.code.bodySlice(extra_index, extra.data.ret_ty.body_len);
                 extra_index += ret_ty_body.len;
             },
         }
@@ -2314,6 +2316,7 @@ const Writer = struct {
             &.{},
             ret_ty_ref,
             ret_ty_body,
+            extra.data.ret_ty.is_generic,
 
             body,
             inst_data.src_node,
@@ -2373,6 +2376,7 @@ const Writer = struct {
             cc_body,
             ret_ty_ref,
             ret_ty_body,
+            extra.data.bits.ret_ty_is_generic,
             body,
             inst_data.src_node,
             src_locs,
@@ -2532,12 +2536,14 @@ const Writer = struct {
         cc_body: []const Zir.Inst.Index,
         ret_ty_ref: Zir.Inst.Ref,
         ret_ty_body: []const Zir.Inst.Index,
+        ret_ty_is_generic: bool,
         body: []const Zir.Inst.Index,
         src_node: i32,
         src_locs: Zir.Inst.Func.SrcLocs,
         noalias_bits: u32,
     ) !void {
         try self.writeOptionalInstRefOrBody(stream, "cc=", cc_ref, cc_body);
+        if (ret_ty_is_generic) try stream.writeAll("[generic] ");
         try self.writeOptionalInstRefOrBody(stream, "ret_ty=", ret_ty_ref, ret_ty_body);
         try self.writeFlag(stream, "vargs, ", var_args);
         try self.writeFlag(stream, "inferror, ", inferred_error_set);
src/Sema.zig
@@ -53,9 +53,6 @@ comptime_break_inst: Zir.Inst.Index = undefined,
 post_hoc_blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, *LabeledBlock) = .empty,
 /// Populated with the last compile error created.
 err: ?*Zcu.ErrorMsg = null,
-/// Set to true when analyzing a func type instruction so that nested generic
-/// function types will emit generic poison instead of a partial type.
-no_partial_func_ty: bool = false,
 
 /// The temporary arena is used for the memory of the `InferredAlloc` values
 /// here so the values can be dropped without any cleanup.
@@ -1935,9 +1932,7 @@ pub fn resolveInstAllowNone(sema: *Sema, zir_ref: Zir.Inst.Ref) !Air.Inst.Ref {
 pub fn resolveInst(sema: *Sema, zir_ref: Zir.Inst.Ref) !Air.Inst.Ref {
     assert(zir_ref != .none);
     if (zir_ref.toIndex()) |i| {
-        const inst = sema.inst_map.get(i).?;
-        if (inst == .generic_poison) return error.GenericPoison;
-        return inst;
+        return sema.inst_map.get(i).?;
     }
     // First section of indexes correspond to a set number of constant values.
     // We intentionally map the same indexes to the same values between ZIR and AIR.
@@ -1997,13 +1992,17 @@ pub fn resolveConstStringIntern(
     return sema.sliceToIpString(block, src, val, reason);
 }
 
-pub fn resolveType(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) !Type {
+fn resolveTypeOrPoison(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) !?Type {
     const air_inst = try sema.resolveInst(zir_ref);
     const ty = try sema.analyzeAsType(block, src, air_inst);
-    if (ty.isGenericPoison()) return error.GenericPoison;
+    if (ty.isGenericPoison()) return null;
     return ty;
 }
 
+pub fn resolveType(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) !Type {
+    return (try sema.resolveTypeOrPoison(block, src, zir_ref)).?;
+}
+
 fn resolveDestType(
     sema: *Sema,
     block: *Block,
@@ -2023,24 +2022,21 @@ fn resolveDestType(
         .remove_eu => false,
     };
 
-    const raw_ty = sema.resolveType(block, src, zir_ref) catch |err| switch (err) {
-        error.GenericPoison => {
-            // Cast builtins use their result type as the destination type, but
-            // it could be an anytype argument, which we can't catch in AstGen.
-            const msg = msg: {
-                const msg = try sema.errMsg(src, "{s} must have a known result type", .{builtin_name});
-                errdefer msg.destroy(sema.gpa);
-                switch (sema.genericPoisonReason(block, zir_ref)) {
-                    .anytype_param => |call_src| try sema.errNote(call_src, msg, "result type is unknown due to anytype parameter", .{}),
-                    .anyopaque_ptr => |ptr_src| try sema.errNote(ptr_src, msg, "result type is unknown due to opaque pointer type", .{}),
-                    .unknown => {},
-                }
-                try sema.errNote(src, msg, "use @as to provide explicit result type", .{});
-                break :msg msg;
-            };
-            return sema.failWithOwnedErrorMsg(block, msg);
-        },
-        else => |e| return e,
+    const raw_ty = try sema.resolveTypeOrPoison(block, src, zir_ref) orelse {
+        // Cast builtins use their result type as the destination type, but
+        // it could be an anytype argument, which we can't catch in AstGen.
+        const msg = msg: {
+            const msg = try sema.errMsg(src, "{s} must have a known result type", .{builtin_name});
+            errdefer msg.destroy(sema.gpa);
+            switch (sema.genericPoisonReason(block, zir_ref)) {
+                .anytype_param => |call_src| try sema.errNote(call_src, msg, "result type is unknown due to anytype parameter", .{}),
+                .anyopaque_ptr => |ptr_src| try sema.errNote(ptr_src, msg, "result type is unknown due to opaque pointer type", .{}),
+                .unknown => {},
+            }
+            try sema.errNote(src, msg, "use @as to provide explicit result type", .{});
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(block, msg);
     };
 
     if (remove_eu and raw_ty.zigTypeTag(zcu) == .error_union) {
@@ -2086,9 +2082,7 @@ fn genericPoisonReason(sema: *Sema, block: *Block, ref: Zir.Inst.Ref) GenericPoi
                 // Either the input type was itself poison, or it was a slice, which we cannot translate
                 // to an overall result type.
                 const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
-                const operand_ref = sema.resolveInst(un_node.operand) catch |err| switch (err) {
-                    error.GenericPoison => unreachable, // this is a type, not a value
-                };
+                const operand_ref = try sema.resolveInst(un_node.operand);
                 if (operand_ref == .generic_poison_type) {
                     // The input was poison -- keep looking.
                     cur = un_node.operand;
@@ -2107,9 +2101,7 @@ fn genericPoisonReason(sema: *Sema, block: *Block, ref: Zir.Inst.Ref) GenericPoi
                 // There are two cases here: the pointer type may already have been
                 // generic poison, or it may have been an anyopaque pointer.
                 const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
-                const operand_ref = sema.resolveInst(un_node.operand) catch |err| switch (err) {
-                    error.GenericPoison => unreachable, // this is a type, not a value
-                };
+                const operand_ref = try sema.resolveInst(un_node.operand);
                 const operand_val = operand_ref.toInterned() orelse return .unknown;
                 if (operand_val == .generic_poison_type) {
                     // The pointer was generic poison - keep looking.
@@ -2187,7 +2179,6 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize)
 }
 
 /// Return the Value corresponding to a given AIR ref, or `null` if it refers to a runtime value.
-/// Generic poison causes `error.GenericPoison` to be returned.
 fn resolveValue(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value {
     const zcu = sema.pt.zcu;
     assert(inst != .none);
@@ -2201,7 +2192,6 @@ fn resolveValue(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value {
 
         assert(val.getVariable(zcu) == null);
         if (val.isPtrRuntimeValue(zcu)) return null;
-        if (val.isGenericPoison()) return error.GenericPoison;
 
         return val;
     } else {
@@ -2761,8 +2751,7 @@ fn zirTupleDecl(
             .elem_index = @intCast(field_index),
         } });
 
-        const uncoerced_field_ty = try sema.resolveInst(zir_field_ty);
-        const field_type = try sema.analyzeAsType(block, type_src, uncoerced_field_ty);
+        const field_type = try sema.resolveType(block, type_src, zir_field_ty);
         try sema.validateTupleFieldType(block, field_type, type_src);
 
         field_ty.* = field_type.toIntern();
@@ -4555,10 +4544,7 @@ fn zirCoercePtrElemTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
     const src = block.nodeOffset(pl_node.src_node);
     const extra = sema.code.extraData(Zir.Inst.Bin, pl_node.payload_index).data;
     const uncoerced_val = try sema.resolveInst(extra.rhs);
-    const maybe_wrapped_ptr_ty = sema.resolveType(block, LazySrcLoc.unneeded, extra.lhs) catch |err| switch (err) {
-        error.GenericPoison => return uncoerced_val,
-        else => |e| return e,
-    };
+    const maybe_wrapped_ptr_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, extra.lhs) orelse return uncoerced_val;
     const ptr_ty = maybe_wrapped_ptr_ty.optEuBaseType(zcu);
     assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
     const elem_ty = ptr_ty.childType(zcu);
@@ -4606,10 +4592,7 @@ fn zirTryOperandTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: boo
     const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
     const src = block.nodeOffset(un_node.src_node);
 
-    const operand_ty = sema.resolveType(block, src, un_node.operand) catch |err| switch (err) {
-        error.GenericPoison => return .generic_poison_type,
-        else => |e| return e,
-    };
+    const operand_ty = try sema.resolveTypeOrPoison(block, src, un_node.operand) orelse return .generic_poison_type;
 
     const payload_ty = if (is_ref) ty: {
         if (!operand_ty.isSinglePointer(zcu)) {
@@ -4656,15 +4639,7 @@ fn zirValidateRefTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
     const src = block.tokenOffset(un_tok.src_tok);
     // In case of GenericPoison, we don't actually have a type, so this will be
     // treated as an untyped address-of operator.
-    const operand_air_inst = sema.resolveInst(un_tok.operand) catch |err| switch (err) {
-        error.GenericPoison => return,
-        else => |e| return e,
-    };
-    const ty_operand = sema.analyzeAsType(block, src, operand_air_inst) catch |err| switch (err) {
-        error.GenericPoison => return,
-        else => |e| return e,
-    };
-    if (ty_operand.isGenericPoison()) return;
+    const ty_operand = try sema.resolveTypeOrPoison(block, src, un_tok.operand) orelse return;
     if (ty_operand.optEuBaseType(zcu).zigTypeTag(zcu) != .pointer) {
         return sema.failWithOwnedErrorMsg(block, msg: {
             const msg = try sema.errMsg(src, "expected type '{}', found pointer", .{ty_operand.fmt(pt)});
@@ -4696,10 +4671,7 @@ fn zirValidateArrayInitRefTy(
     const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
     const src = block.nodeOffset(pl_node.src_node);
     const extra = sema.code.extraData(Zir.Inst.ArrayInitRefTy, pl_node.payload_index).data;
-    const maybe_wrapped_ptr_ty = sema.resolveType(block, LazySrcLoc.unneeded, extra.ptr_ty) catch |err| switch (err) {
-        error.GenericPoison => return .generic_poison_type,
-        else => |e| return e,
-    };
+    const maybe_wrapped_ptr_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, extra.ptr_ty) orelse return .generic_poison_type;
     const ptr_ty = maybe_wrapped_ptr_ty.optEuBaseType(zcu);
     assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
     switch (zcu.intern_pool.indexToKey(ptr_ty.toIntern())) {
@@ -4740,11 +4712,8 @@ fn zirValidateArrayInitTy(
     const src = block.nodeOffset(inst_data.src_node);
     const ty_src: LazySrcLoc = if (is_result_ty) src else block.src(.{ .node_offset_init_ty = inst_data.src_node });
     const extra = sema.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data;
-    const ty = sema.resolveType(block, ty_src, extra.ty) catch |err| switch (err) {
-        // It's okay for the type to be unknown: this will result in an anonymous array init.
-        error.GenericPoison => return,
-        else => |e| return e,
-    };
+    // It's okay for the type to be poison: this will result in an anonymous array init.
+    const ty = try sema.resolveTypeOrPoison(block, ty_src, extra.ty) orelse return;
     const arr_ty = if (is_result_ty) ty.optEuBaseType(zcu) else ty;
     return sema.validateArrayInitTy(block, src, ty_src, extra.init_count, arr_ty);
 }
@@ -4803,11 +4772,8 @@ fn zirValidateStructInitTy(
     const zcu = pt.zcu;
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
     const src = block.nodeOffset(inst_data.src_node);
-    const ty = sema.resolveType(block, src, inst_data.operand) catch |err| switch (err) {
-        // It's okay for the type to be unknown: this will result in an anonymous struct init.
-        error.GenericPoison => return,
-        else => |e| return e,
-    };
+    // It's okay for the type to be poison: this will result in an anonymous struct init.
+    const ty = try sema.resolveTypeOrPoison(block, src, inst_data.operand) orelse return;
     const struct_ty = if (is_result_ty) ty.optEuBaseType(zcu) else ty;
 
     switch (struct_ty.zigTypeTag(zcu)) {
@@ -7043,7 +7009,7 @@ pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref
     const field_name = try zcu.intern_pool.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
     const field_index = sema.structFieldIndex(block, stack_trace_ty, field_name, LazySrcLoc.unneeded) catch |err| switch (err) {
         error.AnalysisFail => @panic("std.builtin.StackTrace is corrupt"),
-        error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
+        error.ComptimeReturn, error.ComptimeBreak => unreachable,
         error.OutOfMemory => |e| return e,
     };
 
@@ -7668,6 +7634,11 @@ fn analyzeCall(
         }
     }
 
+    // This is whether we already know this to be an inline call.
+    // If so, then comptime-known arguments are propagated when evaluating generic parameter/return types.
+    // We might still learn that this call is inline *after* evaluating the generic return type.
+    const early_known_inline = inline_requested or block.isComptime();
+
     // These values are undefined if `func_val == null`.
     const fn_nav: InternPool.Nav, const fn_zir: Zir, const fn_tracked_inst: InternPool.TrackedInst.Index, const fn_zir_inst: Zir.Inst.Index, const fn_zir_info: Zir.FnInfo = if (func_val) |f| b: {
         const info = ip.indexToKey(f.toIntern()).func;
@@ -7746,7 +7717,7 @@ fn analyzeCall(
 
             const extra = sema.code.extraData(Zir.Inst.Param, param_inst.data.pl_tok.payload_index);
             const param_src = generic_block.tokenOffset(param_inst.data.pl_tok.src_tok);
-            const body = sema.code.bodySlice(extra.end, extra.data.body_len);
+            const body = sema.code.bodySlice(extra.end, extra.data.type.body_len);
 
             generic_block.comptime_reason = .{ .reason = .{
                 .r = .{ .simple = .function_parameters },
@@ -7777,8 +7748,10 @@ fn analyzeCall(
             const param_inst_idx = fn_zir_info.param_body[arg_idx];
             const declared_comptime = if (std.math.cast(u5, arg_idx)) |i| func_ty_info.paramIsComptime(i) else false;
             const param_is_comptime = declared_comptime or try arg_ty.comptimeOnlySema(pt);
-            if (param_is_comptime) {
-                if (!try sema.isComptimeKnown(arg.*)) {
+            // We allow comptime-known arguments to propagate to generic types not only for comptime
+            // parameters, but if the call is known to be inline.
+            if (param_is_comptime or early_known_inline) {
+                if (param_is_comptime and !try sema.isComptimeKnown(arg.*)) {
                     assert(!declared_comptime); // `analyzeArg` handles this
                     const arg_src = args_info.argSrc(block, arg_idx);
                     const param_ty_src: LazySrcLoc = .{
@@ -8313,14 +8286,7 @@ fn zirArrayInitElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil
     const pt = sema.pt;
     const zcu = pt.zcu;
     const bin = sema.code.instructions.items(.data)[@intFromEnum(inst)].bin;
-    const maybe_wrapped_indexable_ty = sema.resolveType(block, LazySrcLoc.unneeded, bin.lhs) catch |err| switch (err) {
-        // Since this is a ZIR instruction that returns a type, encountering
-        // generic poison should not result in a failed compilation, but the
-        // generic poison type. This prevents unnecessary failures when
-        // constructing types at compile-time.
-        error.GenericPoison => return .generic_poison_type,
-        else => |e| return e,
-    };
+    const maybe_wrapped_indexable_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, bin.lhs) orelse return .generic_poison_type;
     const indexable_ty = maybe_wrapped_indexable_ty.optEuBaseType(zcu);
     try indexable_ty.resolveFields(pt);
     assert(indexable_ty.isIndexable(zcu)); // validated by a previous instruction
@@ -8337,10 +8303,7 @@ fn zirElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const pt = sema.pt;
     const zcu = pt.zcu;
     const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
-    const maybe_wrapped_ptr_ty = sema.resolveType(block, LazySrcLoc.unneeded, un_node.operand) catch |err| switch (err) {
-        error.GenericPoison => return .generic_poison_type,
-        else => |e| return e,
-    };
+    const maybe_wrapped_ptr_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, un_node.operand) orelse return .generic_poison_type;
     const ptr_ty = maybe_wrapped_ptr_ty.optEuBaseType(zcu);
     assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
     const elem_ty = ptr_ty.childType(zcu);
@@ -8357,10 +8320,7 @@ fn zirIndexablePtrElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
     const zcu = pt.zcu;
     const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
     const src = block.nodeOffset(un_node.src_node);
-    const ptr_ty = sema.resolveType(block, src, un_node.operand) catch |err| switch (err) {
-        error.GenericPoison => return .generic_poison_type,
-        else => |e| return e,
-    };
+    const ptr_ty = try sema.resolveTypeOrPoison(block, src, un_node.operand) orelse return .generic_poison_type;
     try sema.checkMemOperand(block, src, ptr_ty);
     const elem_ty = switch (ptr_ty.ptrSize(zcu)) {
         .slice, .many, .c => ptr_ty.childType(zcu),
@@ -8373,14 +8333,7 @@ fn zirVecArrElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
     const pt = sema.pt;
     const zcu = pt.zcu;
     const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
-    const vec_ty = sema.resolveType(block, LazySrcLoc.unneeded, un_node.operand) catch |err| switch (err) {
-        // Since this is a ZIR instruction that returns a type, encountering
-        // generic poison should not result in a failed compilation, but the
-        // generic poison type. This prevents unnecessary failures when
-        // constructing types at compile-time.
-        error.GenericPoison => return .generic_poison_type,
-        else => |e| return e,
-    };
+    const vec_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, un_node.operand) orelse return .generic_poison_type;
     switch (vec_ty.zigTypeTag(zcu)) {
         .array, .vector => {},
         else => return sema.fail(block, block.nodeOffset(un_node.src_node), "expected array or vector type, found '{}'", .{vec_ty.fmt(pt)}),
@@ -8702,10 +8655,7 @@ fn zirDeclLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index, do_coerce: b
         .no_embedded_nulls,
     );
 
-    const orig_ty = sema.resolveType(block, src, extra.lhs) catch |err| switch (err) {
-        error.GenericPoison => Type.generic_poison,
-        else => |e| return e,
-    };
+    const orig_ty: Type = try sema.resolveTypeOrPoison(block, src, extra.lhs) orelse .generic_poison;
 
     const uncoerced_result = res: {
         if (orig_ty.toIntern() == .generic_poison_type) {
@@ -9232,22 +9182,17 @@ fn zirFunc(
 
     var extra_index = extra.end;
 
-    const ret_ty: Type = switch (extra.data.ret_body_len) {
+    const ret_ty: Type = if (extra.data.ret_ty.is_generic)
+        .generic_poison
+    else switch (extra.data.ret_ty.body_len) {
         0 => Type.void,
         1 => blk: {
             const ret_ty_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
             extra_index += 1;
-            if (sema.resolveType(block, ret_ty_src, ret_ty_ref)) |ret_ty| {
-                break :blk ret_ty;
-            } else |err| switch (err) {
-                error.GenericPoison => {
-                    break :blk Type.generic_poison;
-                },
-                else => |e| return e,
-            }
+            break :blk try sema.resolveType(block, ret_ty_src, ret_ty_ref);
         },
         else => blk: {
-            const ret_ty_body = sema.code.bodySlice(extra_index, extra.data.ret_body_len);
+            const ret_ty_body = sema.code.bodySlice(extra_index, extra.data.ret_ty.body_len);
             extra_index += ret_ty_body.len;
 
             const ret_ty_val = try sema.resolveGenericBody(block, ret_ty_src, ret_ty_body, inst, Type.type, .{ .simple = .function_ret_ty });
@@ -9319,32 +9264,16 @@ fn resolveGenericBody(
 ) !Value {
     assert(body.len != 0);
 
-    const err = err: {
-        // Make sure any nested param instructions don't clobber our work.
-        const prev_params = block.params;
-        const prev_no_partial_func_type = sema.no_partial_func_ty;
-        block.params = .{};
-        sema.no_partial_func_ty = true;
-        defer {
-            block.params = prev_params;
-            sema.no_partial_func_ty = prev_no_partial_func_type;
-        }
-
-        const uncasted = sema.resolveInlineBody(block, body, func_inst) catch |err| break :err err;
-        const result = sema.coerce(block, dest_ty, uncasted, src) catch |err| break :err err;
-        const val = sema.resolveConstDefinedValue(block, src, result, reason) catch |err| break :err err;
-        return val;
-    };
-    switch (err) {
-        error.GenericPoison => {
-            if (dest_ty.toIntern() == .type_type) {
-                return Value.generic_poison_type;
-            } else {
-                return Value.generic_poison;
-            }
-        },
-        else => |e| return e,
+    // Make sure any nested param instructions don't clobber our work.
+    const prev_params = block.params;
+    block.params = .{};
+    defer {
+        block.params = prev_params;
     }
+
+    const uncasted = try sema.resolveInlineBody(block, body, func_inst);
+    const result = try sema.coerce(block, dest_ty, uncasted, src);
+    return sema.resolveConstDefinedValue(block, src, result, reason);
 }
 
 /// Given a library name, examines if the library name should end up in
@@ -9593,8 +9522,6 @@ fn funcCommon(
     const cc_src = block.src(.{ .node_offset_fn_type_cc = src_node_offset });
     const func_src = block.nodeOffset(src_node_offset);
 
-    if (bare_return_type.isGenericPoison() and sema.no_partial_func_ty) return error.GenericPoison;
-
     const ret_ty_requires_comptime = try bare_return_type.comptimeOnlySema(pt);
     var is_generic = bare_return_type.isGenericPoison() or ret_ty_requires_comptime;
 
@@ -9611,9 +9538,6 @@ fn funcCommon(
         } });
         const param_ty_comptime = try param_ty.comptimeOnlySema(pt);
         const param_ty_generic = param_ty.isGenericPoison();
-        if (param_ty_generic and sema.no_partial_func_ty) {
-            return error.GenericPoison;
-        }
         if (param_is_comptime or param_ty_comptime or param_ty_generic) {
             is_generic = true;
         }
@@ -9962,64 +9886,25 @@ fn zirParam(
     const src = block.tokenOffset(inst_data.src_tok);
     const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index);
     const param_name: Zir.NullTerminatedString = extra.data.name;
-    const body = sema.code.bodySlice(extra.end, extra.data.body_len);
+    const body = sema.code.bodySlice(extra.end, extra.data.type.body_len);
 
-    const param_ty = param_ty: {
-        const err = err: {
-            // Make sure any nested param instructions don't clobber our work.
-            const prev_params = block.params;
-            const prev_no_partial_func_type = sema.no_partial_func_ty;
-            block.params = .{};
-            sema.no_partial_func_ty = true;
-            defer {
-                block.params = prev_params;
-                sema.no_partial_func_ty = prev_no_partial_func_type;
-            }
-
-            if (sema.resolveInlineBody(block, body, inst)) |param_ty_inst| {
-                if (sema.analyzeAsType(block, src, param_ty_inst)) |param_ty| {
-                    break :param_ty param_ty;
-                } else |err| break :err err;
-            } else |err| break :err err;
-        };
-        switch (err) {
-            error.GenericPoison => {
-                // The type is not available until the generic instantiation.
-                // We result the param instruction with a poison value and
-                // insert an anytype parameter.
-                try block.params.append(sema.arena, .{
-                    .ty = .generic_poison_type,
-                    .is_comptime = comptime_syntax,
-                    .name = param_name,
-                });
-                sema.inst_map.putAssumeCapacity(inst, .generic_poison);
-                return;
-            },
-            else => |e| return e,
+    const param_ty: Type = if (extra.data.type.is_generic) .generic_poison else ty: {
+        // Make sure any nested param instructions don't clobber our work.
+        const prev_params = block.params;
+        block.params = .{};
+        defer {
+            block.params = prev_params;
         }
-    };
 
-    const is_comptime = try param_ty.comptimeOnlySema(sema.pt) or comptime_syntax;
+        const param_ty_inst = try sema.resolveInlineBody(block, body, inst);
+        break :ty try sema.analyzeAsType(block, src, param_ty_inst);
+    };
 
     try block.params.append(sema.arena, .{
         .ty = param_ty.toIntern(),
         .is_comptime = comptime_syntax,
         .name = param_name,
     });
-
-    if (is_comptime) {
-        // If this is a comptime parameter we can add a constant generic_poison
-        // since this is also a generic parameter.
-        sema.inst_map.putAssumeCapacity(inst, .generic_poison);
-    } else {
-        // Otherwise we need a dummy runtime instruction.
-        const result_index: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
-        try sema.air_instructions.append(sema.gpa, .{
-            .tag = .alloc,
-            .data = .{ .ty = param_ty },
-        });
-        sema.inst_map.putAssumeCapacity(inst, result_index.toRef());
-    }
 }
 
 fn zirParamAnytype(
@@ -10031,14 +9916,11 @@ fn zirParamAnytype(
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
     const param_name: Zir.NullTerminatedString = inst_data.start;
 
-    // We are evaluating a generic function without any comptime args provided.
-
     try block.params.append(sema.arena, .{
         .ty = .generic_poison_type,
         .is_comptime = comptime_syntax,
         .name = param_name,
     });
-    sema.inst_map.putAssumeCapacity(inst, .generic_poison);
 }
 
 fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -10072,24 +9954,11 @@ fn analyzeAs(
     const pt = sema.pt;
     const zcu = pt.zcu;
     const operand = try sema.resolveInst(zir_operand);
-    const operand_air_inst = sema.resolveInst(zir_dest_type) catch |err| switch (err) {
-        error.GenericPoison => return operand,
-        else => |e| return e,
-    };
-    const dest_ty = sema.analyzeAsType(block, src, operand_air_inst) catch |err| switch (err) {
-        error.GenericPoison => return operand,
-        else => |e| return e,
-    };
-    const dest_ty_tag = dest_ty.zigTypeTagOrPoison(zcu) catch |err| switch (err) {
-        error.GenericPoison => return operand,
-    };
-
-    if (dest_ty_tag == .@"opaque") {
-        return sema.fail(block, src, "cannot cast to opaque type '{}'", .{dest_ty.fmt(pt)});
-    }
-
-    if (dest_ty_tag == .noreturn) {
-        return sema.fail(block, src, "cannot cast to noreturn", .{});
+    const dest_ty = try sema.resolveTypeOrPoison(block, src, zir_dest_type) orelse return operand;
+    switch (dest_ty.zigTypeTag(zcu)) {
+        .@"opaque" => return sema.fail(block, src, "cannot cast to opaque type '{}'", .{dest_ty.fmt(pt)}),
+        .noreturn => return sema.fail(block, src, "cannot cast to noreturn", .{}),
+        else => {},
     }
 
     const is_ret = if (zir_dest_type.toIndex()) |ptr_index|
@@ -15071,9 +14940,7 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         // and have a tuple, coerce the tuple immediately.
         no_coerce: {
             if (extra.res_ty == .none) break :no_coerce;
-            const res_ty_inst = try sema.resolveInst(extra.res_ty);
-            const res_ty = try sema.analyzeAsType(block, src, res_ty_inst);
-            if (res_ty.isGenericPoison()) break :no_coerce;
+            const res_ty = try sema.resolveTypeOrPoison(block, src, extra.res_ty) orelse break :no_coerce;
             if (!uncoerced_lhs_ty.isTuple(zcu)) break :no_coerce;
             const lhs_len = uncoerced_lhs_ty.structFieldCount(zcu);
             const lhs_dest_ty = switch (res_ty.zigTypeTag(zcu)) {
@@ -15313,8 +15180,8 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
     try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
 
@@ -15479,8 +15346,8 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
     try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
 
@@ -15645,8 +15512,8 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
     try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
 
@@ -15756,8 +15623,8 @@ fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
     try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
 
@@ -16000,8 +15867,8 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
     try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
 
@@ -16186,8 +16053,8 @@ fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
     try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
 
@@ -16282,8 +16149,8 @@ fn zirRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
     try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
 
@@ -16623,8 +16490,8 @@ fn analyzeArithmetic(
     const zcu = pt.zcu;
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
 
     if (lhs_zig_ty_tag == .pointer) {
@@ -19028,9 +18895,7 @@ fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
     defer child_block.instructions.deinit(sema.gpa);
 
     const operand = try sema.resolveInlineBody(&child_block, body, inst);
-    const operand_ty = sema.typeOf(operand);
-    if (operand_ty.isGenericPoison()) return error.GenericPoison;
-    return Air.internedToRef(operand_ty.toIntern());
+    return Air.internedToRef(sema.typeOf(operand).toIntern());
 }
 
 fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -20100,7 +19965,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
             }
             return err;
         };
-        if (ty.isGenericPoison()) return error.GenericPoison;
+        assert(!ty.isGenericPoison());
         break :blk ty;
     };
 
@@ -20252,11 +20117,8 @@ fn zirStructInitEmptyResult(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is
     const zcu = pt.zcu;
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
     const src = block.nodeOffset(inst_data.src_node);
-    const ty_operand = sema.resolveType(block, src, inst_data.operand) catch |err| switch (err) {
-        // Generic poison means this is an untyped anonymous empty struct/array init
-        error.GenericPoison => return .empty_tuple,
-        else => |e| return e,
-    };
+    // Generic poison means this is an untyped anonymous empty struct/array init
+    const ty_operand = try sema.resolveTypeOrPoison(block, src, inst_data.operand) orelse return .empty_tuple;
     const init_ty = if (is_byref) ty: {
         const ptr_ty = ty_operand.optEuBaseType(zcu);
         assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
@@ -20410,12 +20272,9 @@ fn zirStructInit(
     const first_item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end).data;
     const first_field_type_data = zir_datas[@intFromEnum(first_item.field_type)].pl_node;
     const first_field_type_extra = sema.code.extraData(Zir.Inst.FieldType, first_field_type_data.payload_index).data;
-    const result_ty = sema.resolveType(block, src, first_field_type_extra.container_type) catch |err| switch (err) {
-        error.GenericPoison => {
-            // The type wasn't actually known, so treat this as an anon struct init.
-            return sema.structInitAnon(block, src, inst, .typed_init, extra.data, extra.end, is_ref);
-        },
-        else => |e| return e,
+    const result_ty = try sema.resolveTypeOrPoison(block, src, first_field_type_extra.container_type) orelse {
+        // The type wasn't actually known, so treat this as an anon struct init.
+        return sema.structInitAnon(block, src, inst, .typed_init, extra.data, extra.end, is_ref);
     };
     const resolved_ty = result_ty.optEuBaseType(zcu);
     try resolved_ty.resolveLayout(pt);
@@ -20932,12 +20791,9 @@ fn zirArrayInit(
     const args = sema.code.refSlice(extra.end, extra.data.operands_len);
     assert(args.len >= 2); // array_ty + at least one element
 
-    const result_ty = sema.resolveType(block, src, args[0]) catch |err| switch (err) {
-        error.GenericPoison => {
-            // The type wasn't actually known, so treat this as an anon array init.
-            return sema.arrayInitAnon(block, src, args[1..], is_ref);
-        },
-        else => |e| return e,
+    const result_ty = try sema.resolveTypeOrPoison(block, src, args[0]) orelse {
+        // The type wasn't actually known, so treat this as an anon array init.
+        return sema.arrayInitAnon(block, src, args[1..], is_ref);
     };
     const array_ty = result_ty.optEuBaseType(zcu);
     const is_tuple = array_ty.zigTypeTag(zcu) == .@"struct";
@@ -21185,14 +21041,7 @@ fn zirStructInitFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
     const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data;
     const ty_src = block.nodeOffset(inst_data.src_node);
     const field_name_src = block.src(.{ .node_offset_field_name_init = inst_data.src_node });
-    const wrapped_aggregate_ty = sema.resolveType(block, ty_src, extra.container_type) catch |err| switch (err) {
-        // Since this is a ZIR instruction that returns a type, encountering
-        // generic poison should not result in a failed compilation, but the
-        // generic poison type. This prevents unnecessary failures when
-        // constructing types at compile-time.
-        error.GenericPoison => return .generic_poison_type,
-        else => |e| return e,
-    };
+    const wrapped_aggregate_ty = try sema.resolveTypeOrPoison(block, ty_src, extra.container_type) orelse return .generic_poison_type;
     const aggregate_ty = wrapped_aggregate_ty.optEuBaseType(zcu);
     const zir_field_name = sema.code.nullTerminatedString(extra.name_start);
     const field_name = try ip.getOrPutString(sema.gpa, pt.tid, zir_field_name, .no_embedded_nulls);
@@ -24068,7 +23917,7 @@ fn checkNamespaceType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) Com
 fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    switch (try ty.zigTypeTagOrPoison(zcu)) {
+    switch (ty.zigTypeTag(zcu)) {
         .comptime_int => return true,
         .int => return false,
         else => return sema.fail(block, src, "expected integer type, found '{}'", .{ty.fmt(pt)}),
@@ -24083,7 +23932,7 @@ fn checkInvalidPtrIntArithmetic(
 ) CompileError!void {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    switch (try ty.zigTypeTagOrPoison(zcu)) {
+    switch (ty.zigTypeTag(zcu)) {
         .pointer => switch (ty.ptrSize(zcu)) {
             .one, .slice => return,
             .many, .c => return sema.failWithInvalidPtrArithmetic(block, src, "pointer-integer", "addition and subtraction"),
@@ -24266,7 +24115,7 @@ fn checkAtomicPtrOperand(
     };
 
     const ptr_ty = sema.typeOf(ptr);
-    const ptr_data = switch (try ptr_ty.zigTypeTagOrPoison(zcu)) {
+    const ptr_data = switch (ptr_ty.zigTypeTag(zcu)) {
         .pointer => ptr_ty.ptrInfo(zcu),
         else => {
             const wanted_ptr_ty = try pt.ptrTypeSema(wanted_ptr_data);
@@ -24307,11 +24156,11 @@ fn checkIntOrVector(
     const pt = sema.pt;
     const zcu = pt.zcu;
     const operand_ty = sema.typeOf(operand);
-    switch (try operand_ty.zigTypeTagOrPoison(zcu)) {
+    switch (operand_ty.zigTypeTag(zcu)) {
         .int => return operand_ty,
         .vector => {
             const elem_ty = operand_ty.childType(zcu);
-            switch (try elem_ty.zigTypeTagOrPoison(zcu)) {
+            switch (elem_ty.zigTypeTag(zcu)) {
                 .int => return elem_ty,
                 else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{
                     elem_ty.fmt(pt),
@@ -24332,11 +24181,11 @@ fn checkIntOrVectorAllowComptime(
 ) CompileError!Type {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    switch (try operand_ty.zigTypeTagOrPoison(zcu)) {
+    switch (operand_ty.zigTypeTag(zcu)) {
         .int, .comptime_int => return operand_ty,
         .vector => {
             const elem_ty = operand_ty.childType(zcu);
-            switch (try elem_ty.zigTypeTagOrPoison(zcu)) {
+            switch (elem_ty.zigTypeTag(zcu)) {
                 .int, .comptime_int => return elem_ty,
                 else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{
                     elem_ty.fmt(pt),
@@ -24406,8 +24255,8 @@ fn checkVectorizableBinaryOperands(
 ) CompileError!void {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(zcu);
-    const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(zcu);
+    const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+    const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
     if (lhs_zig_ty_tag != .vector and rhs_zig_ty_tag != .vector) return;
 
     const lhs_is_vector = switch (lhs_zig_ty_tag) {
@@ -24987,7 +24836,7 @@ fn zirSelect(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) C
     const pred_uncoerced = try sema.resolveInst(extra.pred);
     const pred_ty = sema.typeOf(pred_uncoerced);
 
-    const vec_len_u64 = switch (try pred_ty.zigTypeTagOrPoison(zcu)) {
+    const vec_len_u64 = switch (pred_ty.zigTypeTag(zcu)) {
         .vector, .array => pred_ty.arrayLen(zcu),
         else => return sema.fail(block, pred_src, "expected vector or array, found '{}'", .{pred_ty.fmt(pt)}),
     };
@@ -26306,7 +26155,9 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         break :cc .auto;
     };
 
-    const ret_ty: Type = if (extra.data.bits.has_ret_ty_body) blk: {
+    const ret_ty: Type = if (extra.data.bits.ret_ty_is_generic)
+        .generic_poison
+    else if (extra.data.bits.has_ret_ty_body) blk: {
         const body_len = sema.code.extra[extra_index];
         extra_index += 1;
         const body = sema.code.bodySlice(extra_index, body_len);
@@ -26318,14 +26169,8 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
     } else if (extra.data.bits.has_ret_ty_ref) blk: {
         const ret_ty_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
         extra_index += 1;
-        const ret_ty_air_ref = sema.resolveInst(ret_ty_ref) catch |err| switch (err) {
-            error.GenericPoison => break :blk Type.generic_poison,
-            else => |e| return e,
-        };
-        const ret_ty_val = sema.resolveConstDefinedValue(block, ret_src, ret_ty_air_ref, .{ .simple = .function_ret_ty }) catch |err| switch (err) {
-            error.GenericPoison => break :blk Type.generic_poison,
-            else => |e| return e,
-        };
+        const ret_ty_air_ref = try sema.resolveInst(ret_ty_ref);
+        const ret_ty_val = try sema.resolveConstDefinedValue(block, ret_src, ret_ty_air_ref, .{ .simple = .function_ret_ty });
         break :blk ret_ty_val.toType();
     } else Type.void;
 
@@ -27625,7 +27470,7 @@ fn fieldVal(
             const val = (try sema.resolveDefinedValue(block, object_src, dereffed_type)).?;
             const child_type = val.toType();
 
-            switch (try child_type.zigTypeTagOrPoison(zcu)) {
+            switch (child_type.zigTypeTag(zcu)) {
                 .error_set => {
                     switch (ip.indexToKey(child_type.toIntern())) {
                         .error_set_type => |error_set_type| blk: {
@@ -35180,7 +35025,7 @@ pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void {
     if (struct_type.layout == .@"packed") {
         sema.backingIntType(struct_type) catch |err| switch (err) {
             error.OutOfMemory, error.AnalysisFail => |e| return e,
-            error.ComptimeBreak, error.ComptimeReturn, error.GenericPoison => unreachable,
+            error.ComptimeBreak, error.ComptimeReturn => unreachable,
         };
         return;
     }
@@ -35688,7 +35533,7 @@ pub fn resolveStructFieldTypes(
 
     sema.structFields(struct_type) catch |err| switch (err) {
         error.AnalysisFail, error.OutOfMemory => |e| return e,
-        error.ComptimeBreak, error.ComptimeReturn, error.GenericPoison => unreachable,
+        error.ComptimeBreak, error.ComptimeReturn => unreachable,
     };
 }
 
@@ -35717,7 +35562,7 @@ pub fn resolveStructFieldInits(sema: *Sema, ty: Type) SemaError!void {
 
     sema.structFieldInits(struct_type) catch |err| switch (err) {
         error.AnalysisFail, error.OutOfMemory => |e| return e,
-        error.ComptimeBreak, error.ComptimeReturn, error.GenericPoison => unreachable,
+        error.ComptimeBreak, error.ComptimeReturn => unreachable,
     };
     struct_type.setHaveFieldInits(ip);
 }
@@ -35751,7 +35596,7 @@ pub fn resolveUnionFieldTypes(sema: *Sema, ty: Type, union_type: InternPool.Load
     errdefer union_type.setStatus(ip, .none);
     sema.unionFields(ty.toIntern(), union_type) catch |err| switch (err) {
         error.AnalysisFail, error.OutOfMemory => |e| return e,
-        error.ComptimeBreak, error.ComptimeReturn, error.GenericPoison => unreachable,
+        error.ComptimeBreak, error.ComptimeReturn => unreachable,
     };
     union_type.setStatus(ip, .have_field_types);
 }
@@ -36078,9 +35923,6 @@ fn structFields(
             const ty_ref = try sema.resolveInlineBody(&block_scope, body, zir_index);
             break :ty try sema.analyzeAsType(&block_scope, ty_src, ty_ref);
         };
-        if (field_ty.isGenericPoison()) {
-            return error.GenericPoison;
-        }
 
         struct_type.field_types.get(ip)[field_i] = field_ty.toIntern();
 
@@ -36523,10 +36365,6 @@ fn unionFields(
         else
             try sema.resolveType(&block_scope, type_src, field_type_ref);
 
-        if (field_ty.isGenericPoison()) {
-            return error.GenericPoison;
-        }
-
         if (explicit_tags_seen.len > 0) {
             const tag_ty = union_type.tagTypeUnordered(ip);
             const tag_info = ip.loadEnumType(tag_ty);
@@ -36779,7 +36617,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
         .null_type => Value.null,
         .undefined_type => Value.undef,
         .optional_noreturn_type => try pt.nullValue(ty),
-        .generic_poison_type => error.GenericPoison,
+        .generic_poison_type => unreachable,
         .empty_tuple_type => Value.empty_tuple,
         // values, not types
         .undef,
@@ -36797,7 +36635,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
         .bool_true,
         .bool_false,
         .empty_tuple,
-        .generic_poison,
         // invalid
         .none,
         => unreachable,
@@ -38095,7 +37932,6 @@ fn notePathToComptimeAllocPtr(sema: *Sema, msg: *Zcu.ErrorMsg, src: LazySrcLoc,
     ) catch |err| switch (err) {
         error.OutOfMemory => |e| return e,
         error.AnalysisFail => unreachable,
-        error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         error.ComptimeBreak => unreachable,
     };
@@ -38410,7 +38246,6 @@ pub fn resolveDeclaredEnum(
         zir,
         body_end,
     ) catch |err| switch (err) {
-        error.GenericPoison => unreachable,
         error.ComptimeBreak => unreachable,
         error.ComptimeReturn => unreachable,
         error.OutOfMemory => |e| return e,
src/Type.zig
@@ -22,11 +22,7 @@ const SemaError = Zcu.SemaError;
 ip_index: InternPool.Index,
 
 pub fn zigTypeTag(ty: Type, zcu: *const Zcu) std.builtin.TypeId {
-    return ty.zigTypeTagOrPoison(zcu) catch unreachable;
-}
-
-pub fn zigTypeTagOrPoison(ty: Type, zcu: *const Zcu) error{GenericPoison}!std.builtin.TypeId {
-    return zcu.intern_pool.zigTypeTagOrPoison(ty.toIntern());
+    return zcu.intern_pool.zigTypeTag(ty.toIntern());
 }
 
 pub fn baseZigTypeTag(self: Type, mod: *Zcu) std.builtin.TypeId {
@@ -2503,14 +2499,16 @@ pub fn fnCallingConvention(ty: Type, zcu: *const Zcu) std.builtin.CallingConvent
 }
 
 pub fn isValidParamType(self: Type, zcu: *const Zcu) bool {
-    return switch (self.zigTypeTagOrPoison(zcu) catch return true) {
+    if (self.toIntern() == .generic_poison_type) return true;
+    return switch (self.zigTypeTag(zcu)) {
         .@"opaque", .noreturn => false,
         else => true,
     };
 }
 
 pub fn isValidReturnType(self: Type, zcu: *const Zcu) bool {
-    return switch (self.zigTypeTagOrPoison(zcu) catch return true) {
+    if (self.toIntern() == .generic_poison_type) return true;
+    return switch (self.zigTypeTag(zcu)) {
         .@"opaque" => false,
         else => true,
     };
@@ -3784,7 +3782,6 @@ pub fn resolveFields(ty: Type, pt: Zcu.PerThread) SemaError!void {
         .bool_true => unreachable,
         .bool_false => unreachable,
         .empty_tuple => unreachable,
-        .generic_poison => unreachable,
 
         else => switch (ty_ip.unwrap(ip).getTag(ip)) {
             .type_struct,
src/Value.zig
@@ -3673,10 +3673,6 @@ pub fn hasRepeatedByteRepr(val: Value, pt: Zcu.PerThread) !?u8 {
     return first_byte;
 }
 
-pub fn isGenericPoison(val: Value) bool {
-    return val.toIntern() == .generic_poison;
-}
-
 pub fn typeOf(val: Value, zcu: *const Zcu) Type {
     return Type.fromInterned(zcu.intern_pool.typeOf(val.toIntern()));
 }
@@ -3709,7 +3705,6 @@ pub const @"false": Value = .{ .ip_index = .bool_false };
 pub const @"true": Value = .{ .ip_index = .bool_true };
 pub const @"unreachable": Value = .{ .ip_index = .unreachable_value };
 
-pub const generic_poison: Value = .{ .ip_index = .generic_poison };
 pub const generic_poison_type: Value = .{ .ip_index = .generic_poison_type };
 pub const empty_tuple: Value = .{ .ip_index = .empty_tuple };
 
src/Zcu.zig
@@ -2404,10 +2404,6 @@ pub const CompileError = error{
     OutOfMemory,
     /// When this is returned, the compile error for the failure has already been recorded.
     AnalysisFail,
-    /// A Type or Value was needed to be used during semantic analysis, but it was not available
-    /// because the function is generic. This is only seen when analyzing the body of a param
-    /// instruction.
-    GenericPoison,
     /// In a comptime scope, a return instruction was encountered. This error is only seen when
     /// doing a comptime function call.
     ComptimeReturn,
test/behavior/fn.zig
@@ -672,3 +672,42 @@ test "function parameter self equality" {
     try expect(!S.greaterThan(42));
     try expect(S.greaterThanOrEqual(42));
 }
+
+test "inline call propagates comptime-known argument to generic parameter and return types" {
+    const S = struct {
+        inline fn f(x: bool, y: if (x) u8 else u16) if (x) bool else u32 {
+            if (x) {
+                comptime assert(@TypeOf(y) == u8);
+                return y == 0;
+            } else {
+                comptime assert(@TypeOf(y) == u16);
+                return y * 10;
+            }
+        }
+        fn g(x: bool, y: if (x) u8 else u16) if (x) bool else u32 {
+            if (x) {
+                comptime assert(@TypeOf(y) == u8);
+                return y == 0;
+            } else {
+                comptime assert(@TypeOf(y) == u16);
+                return y * 10;
+            }
+        }
+    };
+
+    const a0 = S.f(true, 200); // false
+    const a1 = S.f(false, 1234); // 12340
+
+    const b0 = @call(.always_inline, S.g, .{ true, 200 }); // false
+    const b1 = @call(.always_inline, S.g, .{ false, 1234 }); // 12340
+
+    comptime assert(@TypeOf(a0) == bool);
+    comptime assert(@TypeOf(b0) == bool);
+    try expect(a0 == false);
+    try expect(b0 == false);
+
+    comptime assert(@TypeOf(a1) == u32);
+    comptime assert(@TypeOf(b1) == u32);
+    try expect(a1 == 12340);
+    try expect(b1 == 12340);
+}
test/cases/compile_errors/anytype_param_requires_comptime.zig
@@ -15,6 +15,6 @@ pub export fn entry() void {
 // error
 //
 // :7:25: error: unable to resolve comptime value
-// :7:25: note: initializer of comptime-only struct 'tmp.S.foo__anon_166.C' must be comptime-known
+// :7:25: note: initializer of comptime-only struct 'tmp.S.foo__anon_165.C' must be comptime-known
 // :4:16: note: struct requires comptime because of this field
 // :4:16: note: types are not available at runtime
test/cases/compile_errors/bogus_method_call_on_slice.zig
@@ -13,10 +13,8 @@ pub export fn entry2() void {
 }
 
 // error
-// backend=stage2
-// target=native
 //
 // :3:6: error: no field or member function named 'copy' in '[]const u8'
 // :9:8: error: no field or member function named 'bar' in '@TypeOf(.{})'
-// :12:18: error: no field or member function named 'bar' in 'tmp.entry2__struct_170'
+// :12:18: error: no field or member function named 'bar' in 'tmp.entry2__struct_169'
 // :12:6: note: struct declared here
test/cases/compile_errors/coerce_anon_struct.zig
@@ -6,6 +6,6 @@ export fn foo() void {
 
 // error
 //
-// :4:16: error: expected type 'tmp.T', found 'tmp.foo__struct_159'
+// :4:16: error: expected type 'tmp.T', found 'tmp.foo__struct_158'
 // :3:16: note: struct declared here
 // :1:11: note: struct declared here
test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig
@@ -8,7 +8,7 @@ export fn entry() void {
 fn makeLlamas(count: usize) [count]u8 {}
 
 // error
-// target=native
 //
 // :8:30: error: unable to resolve comptime value
 // :8:30: note: array length must be comptime-known
+// :2:31: note: called from here