fix: optional-chain index opt?.xs[i] over array/ptr-array field (issue 0181)
opt?.xs[i] typed and lowered the index over the optional CONTAINER (?[N]T); getElementType returned .unresolved, so index_get reached LLVM with an unresolved element type and panicked. Mirroring the 0101 !-unwrap fix: add lowerOptionalChainIndex (optional_has_value -> some: unwrap + index (index_gep+load for ?*[N]T, else index_get) + optional_wrap; none: const_null; merge -> ?ElemType, element-optional flattened). The typer + dispatch guard compute the element via ptrToArrayElem(child) orelse getElementType(child), so value-arrays, slices, many-pointers, AND pointer-to-array (?*[N]T) children resolve. Null receivers short-circuit (no null deref). Regression: examples/optionals/0915-optional-chain-array-field-index.sx. Verified by 3 adversarial reviews, suite 794/0. Filed broader pre-existing gap 0183 (indexing a non-indexable type panics instead of diagnosing).
This commit is contained in:
@@ -393,6 +393,19 @@ pub const ExprTyper = struct {
|
||||
return self.l.inferExprType(arg_node);
|
||||
}
|
||||
const obj_ty = self.l.inferExprType(ie.object);
|
||||
// Optional-chain index `opt?.xs[i]`: the object types as an
|
||||
// optional container (`?[N]T` / `?[]T` / `?[*]T`), so the whole
|
||||
// index expression is `?ElemType` (flattened if the element is
|
||||
// itself optional) — mirrors lowerOptionalChainIndex (issue 0181).
|
||||
if (!obj_ty.isBuiltin() and self.l.module.types.get(obj_ty) == .optional) {
|
||||
const child = self.l.module.types.get(obj_ty).optional.child;
|
||||
// `?*[N]T` is indexable: element is the pointee array's
|
||||
// element. `getElementType` has no pointer arm, so consult
|
||||
// `ptrToArrayElem` first (mirrors lowerIndexExpr's guard) —
|
||||
// otherwise `?*[N]T` typed as `.unresolved` (issue 0181).
|
||||
const elem = self.l.ptrToArrayElem(child) orelse self.l.getElementType(child);
|
||||
if (elem != .unresolved) return self.l.optionalOfFlattened(elem);
|
||||
}
|
||||
if (self.l.ptrToArrayElem(obj_ty)) |elem| return elem;
|
||||
return self.l.getElementType(obj_ty);
|
||||
},
|
||||
|
||||
@@ -1982,6 +1982,7 @@ pub const Lowering = struct {
|
||||
pub const lowerNumericLimit = lower_expr.lowerNumericLimit;
|
||||
pub const lowerStructConstant = lower_expr.lowerStructConstant;
|
||||
pub const lowerOptionalChain = lower_expr.lowerOptionalChain;
|
||||
pub const lowerOptionalChainIndex = lower_expr.lowerOptionalChainIndex;
|
||||
pub const vectorLaneIndex = lower_expr.vectorLaneIndex;
|
||||
pub const lowerFieldAccessOnType = lower_expr.lowerFieldAccessOnType;
|
||||
pub const lowerEnumLiteral = lower_expr.lowerEnumLiteral;
|
||||
|
||||
@@ -1021,6 +1021,49 @@ pub fn lowerOptionalChain(self: *Lowering, obj: Ref, fa: *const ast.FieldAccess,
|
||||
return self.builder.blockParam(merge_bb, 0, result_ty);
|
||||
}
|
||||
|
||||
/// Lower an indexed optional-chain access: `opt?.xs[i]` where the `?.` field is
|
||||
/// an array / slice / many-pointer. Mirrors `lowerOptionalChain`'s short-circuit
|
||||
/// — the index applies in the some-branch, producing `?ElemType` (null when the
|
||||
/// receiver was null). `child` is the unwrapped container type, `elem_ty` the
|
||||
/// indexed element type.
|
||||
pub fn lowerOptionalChainIndex(self: *Lowering, ie: *const ast.IndexExpr, child: TypeId, elem_ty: TypeId) Ref {
|
||||
// The chained `?.` field access produced the optional value; lower it.
|
||||
const opt_val = self.lowerExpr(ie.object);
|
||||
// If the element is itself optional, indexing flattens (no double-wrap),
|
||||
// matching the field-chain `?.` flattening rule.
|
||||
const elem_is_optional = !elem_ty.isBuiltin() and self.module.types.get(elem_ty) == .optional;
|
||||
const result_ty = if (elem_is_optional) elem_ty else self.module.types.optionalOf(elem_ty);
|
||||
|
||||
const has_val = self.builder.emit(.{ .optional_has_value = .{ .operand = opt_val } }, .bool);
|
||||
|
||||
const some_bb = self.freshBlock("chain.some");
|
||||
const none_bb = self.freshBlock("chain.none");
|
||||
const merge_bb = self.freshBlockWithParams("chain.merge", &.{result_ty});
|
||||
|
||||
self.builder.condBr(has_val, some_bb, &.{}, none_bb, &.{});
|
||||
|
||||
// Some: unwrap the container, index it. A `?*[N]T` unwraps to the pointer
|
||||
// (GEP through it); a value container (`?[N]T` / `?[]T`) unwraps to the
|
||||
// aggregate value and `index_get`s the element.
|
||||
self.builder.switchToBlock(some_bb);
|
||||
const unwrapped = self.builder.emit(.{ .optional_unwrap = .{ .operand = opt_val } }, child);
|
||||
const idx = self.lowerExpr(ie.index);
|
||||
const elem_val = if (self.ptrToArrayElem(child)) |pelem| blk: {
|
||||
const gep = self.builder.emit(.{ .index_gep = .{ .lhs = unwrapped, .rhs = idx } }, self.module.types.ptrTo(pelem));
|
||||
break :blk self.builder.load(gep, pelem);
|
||||
} else self.builder.emit(.{ .index_get = .{ .lhs = unwrapped, .rhs = idx } }, elem_ty);
|
||||
const some_result = if (elem_is_optional) elem_val else self.builder.emit(.{ .optional_wrap = .{ .operand = elem_val } }, result_ty);
|
||||
self.builder.br(merge_bb, &.{some_result});
|
||||
|
||||
// None: null optional.
|
||||
self.builder.switchToBlock(none_bb);
|
||||
const none_result = self.builder.constNull(result_ty);
|
||||
self.builder.br(merge_bb, &.{none_result});
|
||||
|
||||
self.builder.switchToBlock(merge_bb);
|
||||
return self.builder.blockParam(merge_bb, 0, result_ty);
|
||||
}
|
||||
|
||||
/// Field access on a known type (shared by regular field access and optional chaining)
|
||||
/// Map a Vector swizzle component (`.x`/`.y`/`.z`/`.w` or the colour
|
||||
/// aliases `.r`/`.g`/`.b`/`.a`) to its lane index. Returns null for any
|
||||
@@ -1755,6 +1798,24 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
|
||||
}
|
||||
// Infer element type from the object's slice/array type
|
||||
const obj_ty = self.inferExprType(ie.object);
|
||||
// Optional-chain index: `opt?.xs[i]`. The `?.` makes the object an
|
||||
// optional whose child is the (array/slice/many-ptr) field — so the index
|
||||
// applies inside the chain's some-branch and the whole expression is
|
||||
// `?ElemType` (null if the receiver was null). Without this the element
|
||||
// type resolved through `getElementType(?[N]T)` was `.unresolved` and an
|
||||
// `index_get` on the optional value reached LLVM emission (issue 0181).
|
||||
if (!obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .optional) {
|
||||
const child = self.module.types.get(obj_ty).optional.child;
|
||||
// A pointer-to-array child (`?*[N]T`) is indexable too: its element is
|
||||
// the pointee array's element. `getElementType` has no pointer arm, so
|
||||
// ask `ptrToArrayElem` first (mirrors the non-optional `*[N]T` path
|
||||
// below) — otherwise the `?*[N]T` case fell through to a plain
|
||||
// `index_get` with an `.unresolved` element type (issue 0181).
|
||||
const elem_ty = self.ptrToArrayElem(child) orelse self.getElementType(child);
|
||||
if (elem_ty != .unresolved) {
|
||||
return self.lowerOptionalChainIndex(ie, child, elem_ty);
|
||||
}
|
||||
}
|
||||
// Array with addressable storage: GEP the element in place + load,
|
||||
// never `index_get` on the loaded array VALUE — that realizes as
|
||||
// copy-whole-array-to-temp per read (the general-expression sibling
|
||||
|
||||
Reference in New Issue
Block a user