Add auto-deref support for nested patterns through pointers
9748e26739a6526a8fa6c18fb2370e6146a69a40f4de9a6df14e3b9c1d3a146d
When a union variant field or record field holds a pointer (*T) and the pattern is a destructuring pattern (Call, RecordLit, or ScopeAccess), the compiler now automatically dereferences the pointer and matches against the pointed-to type. This follows Rust's box-pattern semantics. Resolver: added isDestructuringPattern() helper and Type::Pointer case in resolveCasePattern() that auto-derefs for destructuring patterns. Lowerer: modified bindPatternVariables() to get the actual field type from the variant's record info (preserving pointer types for correct data layout), and added auto-deref handling in bindFieldVariable() and emitNestedFieldTest().
1 parent
19abdd4f
lib/std/arch/rv64/tests/match.nested.deref.rad
added
+156 -0
| 1 | + | //! Test nested patterns through pointer dereferences (auto-deref). |
|
| 2 | + | ||
| 3 | + | union Inner { |
|
| 4 | + | A(i32), |
|
| 5 | + | B, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Outer { |
|
| 9 | + | Some(*Inner), |
|
| 10 | + | None, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Match nested union variant through pointer dereference in match/case. |
|
| 14 | + | fn matchDeref(o: Outer) -> i32 { |
|
| 15 | + | match o { |
|
| 16 | + | case Outer::Some(Inner::A(x)) => { |
|
| 17 | + | return x; |
|
| 18 | + | } |
|
| 19 | + | case Outer::Some(Inner::B) => { |
|
| 20 | + | return -1; |
|
| 21 | + | } |
|
| 22 | + | else => { |
|
| 23 | + | return -2; |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// If-let-case with auto-deref nested pattern. |
|
| 29 | + | fn ifLetDeref(o: Outer) -> i32 { |
|
| 30 | + | if let case Outer::Some(Inner::A(x)) = o { |
|
| 31 | + | return x; |
|
| 32 | + | } |
|
| 33 | + | return 0; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Let-else with auto-deref nested pattern. |
|
| 37 | + | fn letElseDeref(o: Outer) -> i32 { |
|
| 38 | + | let case Outer::Some(Inner::A(x)) = o |
|
| 39 | + | else { return -1; }; |
|
| 40 | + | return x; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /// Record with pointer field and nested pattern through deref. |
|
| 44 | + | record Container { |
|
| 45 | + | inner: *Inner, |
|
| 46 | + | tag: i32, |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | union Boxed { |
|
| 50 | + | Some { c: Container }, |
|
| 51 | + | None, |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | /// Nested record with auto-deref on a pointer field. |
|
| 55 | + | fn matchRecordDeref(b: Boxed) -> i32 { |
|
| 56 | + | match b { |
|
| 57 | + | case Boxed::Some { c: Container { inner: Inner::A(val), tag } } => { |
|
| 58 | + | return val + tag; |
|
| 59 | + | } |
|
| 60 | + | else => { |
|
| 61 | + | return 0; |
|
| 62 | + | } |
|
| 63 | + | } |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Auto-deref through pointer in if-let-case with record. |
|
| 67 | + | fn ifLetRecordDeref(b: Boxed) -> i32 { |
|
| 68 | + | if let case Boxed::Some { c: Container { inner: Inner::A(val), tag } } = b { |
|
| 69 | + | return val + tag; |
|
| 70 | + | } |
|
| 71 | + | return 0; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Void variant through pointer deref. |
|
| 75 | + | fn matchDerefVoid(o: Outer) -> i32 { |
|
| 76 | + | if let case Outer::Some(Inner::B) = o { |
|
| 77 | + | return 1; |
|
| 78 | + | } |
|
| 79 | + | return 0; |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// Auto-deref with placeholder in nested pattern. |
|
| 83 | + | fn matchDerefPlaceholder(o: Outer) -> i32 { |
|
| 84 | + | if let case Outer::Some(Inner::A(_)) = o { |
|
| 85 | + | return 1; |
|
| 86 | + | } |
|
| 87 | + | return 0; |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Auto-deref through pointer to record in a record field. |
|
| 91 | + | record Point { |
|
| 92 | + | x: i32, |
|
| 93 | + | y: i32, |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | union Holder { |
|
| 97 | + | Ptr { p: *Point, z: i32 }, |
|
| 98 | + | Empty, |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | fn derefRecordField(h: Holder) -> i32 { |
|
| 102 | + | if let case Holder::Ptr { p: Point { x, y }, z } = h { |
|
| 103 | + | return x + y + z; |
|
| 104 | + | } |
|
| 105 | + | return 0; |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | @default fn main() -> i32 { |
|
| 109 | + | let innerA = Inner::A(42); |
|
| 110 | + | let innerB = Inner::B; |
|
| 111 | + | ||
| 112 | + | // matchDeref |
|
| 113 | + | assert matchDeref(Outer::Some(&innerA)) == 42; |
|
| 114 | + | assert matchDeref(Outer::Some(&innerB)) == -1; |
|
| 115 | + | assert matchDeref(Outer::None) == -2; |
|
| 116 | + | ||
| 117 | + | // ifLetDeref |
|
| 118 | + | assert ifLetDeref(Outer::Some(&innerA)) == 42; |
|
| 119 | + | assert ifLetDeref(Outer::Some(&innerB)) == 0; |
|
| 120 | + | assert ifLetDeref(Outer::None) == 0; |
|
| 121 | + | ||
| 122 | + | // letElseDeref |
|
| 123 | + | assert letElseDeref(Outer::Some(&innerA)) == 42; |
|
| 124 | + | assert letElseDeref(Outer::Some(&innerB)) == -1; |
|
| 125 | + | assert letElseDeref(Outer::None) == -1; |
|
| 126 | + | ||
| 127 | + | // matchRecordDeref |
|
| 128 | + | let c1 = Boxed::Some { c: Container { inner: &innerA, tag: 10 } }; |
|
| 129 | + | assert matchRecordDeref(c1) == 52; |
|
| 130 | + | let c2 = Boxed::Some { c: Container { inner: &innerB, tag: 20 } }; |
|
| 131 | + | assert matchRecordDeref(c2) == 0; |
|
| 132 | + | assert matchRecordDeref(Boxed::None) == 0; |
|
| 133 | + | ||
| 134 | + | // ifLetRecordDeref |
|
| 135 | + | assert ifLetRecordDeref(c1) == 52; |
|
| 136 | + | assert ifLetRecordDeref(c2) == 0; |
|
| 137 | + | assert ifLetRecordDeref(Boxed::None) == 0; |
|
| 138 | + | ||
| 139 | + | // matchDerefVoid |
|
| 140 | + | assert matchDerefVoid(Outer::Some(&innerA)) == 0; |
|
| 141 | + | assert matchDerefVoid(Outer::Some(&innerB)) == 1; |
|
| 142 | + | assert matchDerefVoid(Outer::None) == 0; |
|
| 143 | + | ||
| 144 | + | // matchDerefPlaceholder |
|
| 145 | + | assert matchDerefPlaceholder(Outer::Some(&innerA)) == 1; |
|
| 146 | + | assert matchDerefPlaceholder(Outer::Some(&innerB)) == 0; |
|
| 147 | + | assert matchDerefPlaceholder(Outer::None) == 0; |
|
| 148 | + | ||
| 149 | + | // derefRecordField |
|
| 150 | + | let pt = Point { x: 3, y: 4 }; |
|
| 151 | + | let h1 = Holder::Ptr { p: &pt, z: 5 }; |
|
| 152 | + | assert derefRecordField(h1) == 12; |
|
| 153 | + | assert derefRecordField(Holder::Empty) == 0; |
|
| 154 | + | ||
| 155 | + | return 0; |
|
| 156 | + | } |
lib/std/lang/lower.rad
+72 -26
| 2941 | 2941 | case ast::NodeValue::Placeholder => return true, |
|
| 2942 | 2942 | else => return false, |
|
| 2943 | 2943 | } |
|
| 2944 | 2944 | } |
|
| 2945 | 2945 | ||
| 2946 | + | /// Check whether a pattern node is a destructuring pattern that looks |
|
| 2947 | + | /// through structure (union variant, record literal, scope access). |
|
| 2948 | + | /// Identifiers, placeholders, and plain literals are not destructuring. |
|
| 2949 | + | fn isDestructuringPattern(pattern: *ast::Node) -> bool { |
|
| 2950 | + | match pattern.value { |
|
| 2951 | + | case ast::NodeValue::Call(_), |
|
| 2952 | + | ast::NodeValue::RecordLit(_), |
|
| 2953 | + | ast::NodeValue::ScopeAccess(_) => return true, |
|
| 2954 | + | else => return false, |
|
| 2955 | + | } |
|
| 2956 | + | } |
|
| 2957 | + | ||
| 2946 | 2958 | /// Check whether an AST node is the `undefined` literal. |
|
| 2947 | 2959 | fn isUndef(node: *ast::Node) -> bool { |
|
| 2948 | 2960 | match node.value { |
|
| 2949 | 2961 | case ast::NodeValue::Undef => return true, |
|
| 2950 | 2962 | else => return false, |
| 3064 | 3076 | fn bindPatternVariables(self: *mut FnLowerer, subject: *MatchSubject, patterns: *mut [*ast::Node], failBlock: BlockId) throws (LowerError) { |
|
| 3065 | 3077 | for pattern in patterns { |
|
| 3066 | 3078 | ||
| 3067 | 3079 | // Handle simple variant patterns like `Variant(x)`. |
|
| 3068 | 3080 | if let arg = resolver::variantPatternBinding(self.low.resolver, pattern) { |
|
| 3069 | - | if let bindType = resolver::typeFor(self.low.resolver, arg) { |
|
| 3070 | - | let case MatchSubjectKind::Union(unionInfo) = subject.kind |
|
| 3071 | - | else panic "bindPatternVariables: expected union subject"; |
|
| 3072 | - | let valOffset = unionInfo.valOffset as i32; |
|
| 3073 | - | ||
| 3074 | - | match arg.value { |
|
| 3075 | - | case ast::NodeValue::Ident(name) => { |
|
| 3076 | - | try bindPayloadVariable(self, name, subject.val, bindType, subject.by, valOffset, false); |
|
| 3077 | - | } |
|
| 3078 | - | case ast::NodeValue::Placeholder => {} |
|
| 3079 | - | else => { |
|
| 3080 | - | // Nested pattern inside a variant call, e.g. `Variant(Inner { x, y })`. |
|
| 3081 | - | let base = emitValToReg(self, subject.val); |
|
| 3082 | - | let payloadBase = emitPtrOffset(self, base, valOffset); |
|
| 3083 | - | let fieldInfo = resolver::RecordField { |
|
| 3084 | - | name: nil, |
|
| 3085 | - | fieldType: bindType, |
|
| 3086 | - | offset: 0, |
|
| 3087 | - | }; |
|
| 3088 | - | try bindFieldVariable(self, arg, payloadBase, fieldInfo, subject.by, failBlock); |
|
| 3089 | - | } |
|
| 3081 | + | let case MatchSubjectKind::Union(unionInfo) = subject.kind |
|
| 3082 | + | else panic "bindPatternVariables: expected union subject"; |
|
| 3083 | + | let valOffset = unionInfo.valOffset as i32; |
|
| 3084 | + | ||
| 3085 | + | // Get the actual field type from the variant's record info. |
|
| 3086 | + | // This preserves the original data layout type (e.g. `*T`) even when |
|
| 3087 | + | // the resolver resolved the pattern against a dereferenced type (`T`). |
|
| 3088 | + | let variantExtra = resolver::nodeData(self.low.resolver, pattern).extra; |
|
| 3089 | + | let case resolver::NodeExtra::UnionVariant { ordinal, .. } = variantExtra |
|
| 3090 | + | else panic "bindPatternVariables: expected variant extra"; |
|
| 3091 | + | let payloadType = unionInfo.variants[ordinal].valueType; |
|
| 3092 | + | let payloadRec = resolver::getRecord(payloadType) |
|
| 3093 | + | else panic "bindPatternVariables: expected record payload"; |
|
| 3094 | + | let fieldType = payloadRec.fields[0].fieldType; |
|
| 3095 | + | ||
| 3096 | + | match arg.value { |
|
| 3097 | + | case ast::NodeValue::Ident(name) => { |
|
| 3098 | + | try bindPayloadVariable(self, name, subject.val, fieldType, subject.by, valOffset, false); |
|
| 3099 | + | } |
|
| 3100 | + | case ast::NodeValue::Placeholder => {} |
|
| 3101 | + | else => { |
|
| 3102 | + | // Nested pattern inside a variant call, e.g. `Variant(Inner { x, y })`. |
|
| 3103 | + | let base = emitValToReg(self, subject.val); |
|
| 3104 | + | let payloadBase = emitPtrOffset(self, base, valOffset); |
|
| 3105 | + | let fieldInfo = resolver::RecordField { |
|
| 3106 | + | name: nil, |
|
| 3107 | + | fieldType, |
|
| 3108 | + | offset: 0, |
|
| 3109 | + | }; |
|
| 3110 | + | try bindFieldVariable(self, arg, payloadBase, fieldInfo, subject.by, failBlock); |
|
| 3090 | 3111 | } |
|
| 3091 | 3112 | } |
|
| 3092 | 3113 | } |
|
| 3093 | 3114 | match pattern.value { |
|
| 3094 | 3115 | // Compound variant patterns like `Variant { a, b }`. |
| 3192 | 3213 | try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock); |
|
| 3193 | 3214 | return; |
|
| 3194 | 3215 | } |
|
| 3195 | 3216 | } |
|
| 3196 | 3217 | // Plain nested record destructuring pattern. |
|
| 3197 | - | let recInfo = resolver::getRecord(fieldInfo.fieldType) |
|
| 3218 | + | // Auto-deref: if the field is a pointer, load it first. |
|
| 3219 | + | let mut derefType = fieldInfo.fieldType; |
|
| 3220 | + | let mut nestedBase = emitPtrOffset(self, base, fieldInfo.offset); |
|
| 3221 | + | if let case resolver::Type::Pointer { target, .. } = fieldInfo.fieldType { |
|
| 3222 | + | let ptrReg = nextReg(self); |
|
| 3223 | + | emitLoadW64(self, ptrReg, nestedBase); |
|
| 3224 | + | nestedBase = ptrReg; |
|
| 3225 | + | derefType = *target; |
|
| 3226 | + | } |
|
| 3227 | + | let recInfo = resolver::getRecord(derefType) |
|
| 3198 | 3228 | else throw LowerError::ExpectedRecord; |
|
| 3199 | - | let nestedBase = emitPtrOffset(self, base, fieldInfo.offset); |
|
| 3200 | 3229 | try bindNestedRecordFields(self, nestedBase, lit, recInfo, matchBy, failBlock); |
|
| 3201 | 3230 | } |
|
| 3202 | 3231 | else => { |
|
| 3203 | 3232 | // Nested pattern requiring a test (union variant scope access, literal, etc). |
|
| 3204 | 3233 | try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock); |
| 3215 | 3244 | base: il::Reg, |
|
| 3216 | 3245 | fieldInfo: resolver::RecordField, |
|
| 3217 | 3246 | matchBy: resolver::MatchBy, |
|
| 3218 | 3247 | failBlock: BlockId |
|
| 3219 | 3248 | ) throws (LowerError) { |
|
| 3220 | - | let fieldType = fieldInfo.fieldType; |
|
| 3249 | + | let mut fieldType = fieldInfo.fieldType; |
|
| 3221 | 3250 | let fieldPtr = emitPtrOffset(self, base, fieldInfo.offset); |
|
| 3222 | 3251 | ||
| 3252 | + | // Auto-deref: when the field is a pointer and the pattern destructures |
|
| 3253 | + | // the pointed-to value, load the pointer and use the target type. |
|
| 3254 | + | // The loaded pointer becomes the base address for the nested subject. |
|
| 3255 | + | let mut derefBase: ?il::Reg = nil; |
|
| 3256 | + | if let case resolver::Type::Pointer { target, .. } = fieldType { |
|
| 3257 | + | if isDestructuringPattern(pattern) { |
|
| 3258 | + | let ptrReg = nextReg(self); |
|
| 3259 | + | emitLoadW64(self, ptrReg, fieldPtr); |
|
| 3260 | + | derefBase = ptrReg; |
|
| 3261 | + | fieldType = *target; |
|
| 3262 | + | } |
|
| 3263 | + | } |
|
| 3264 | + | ||
| 3223 | 3265 | // Build a MatchSubject for the nested field. |
|
| 3224 | 3266 | let ilTy = ilType(self.low, fieldType); |
|
| 3225 | 3267 | let kind = matchSubjectKind(fieldType); |
|
| 3226 | 3268 | ||
| 3227 | - | // For aggregate subjects, the val must be a pointer (Reg). |
|
| 3269 | + | // Determine the subject value. |
|
| 3228 | 3270 | let mut val = il::Val::Reg(fieldPtr); |
|
| 3229 | - | if not isAggregateType(fieldType) { |
|
| 3271 | + | if let reg = derefBase { |
|
| 3272 | + | // Auto-deref: the loaded pointer is the address of the target value. |
|
| 3273 | + | val = il::Val::Reg(reg); |
|
| 3274 | + | } else if not isAggregateType(fieldType) { |
|
| 3275 | + | // Scalar: load the value. |
|
| 3230 | 3276 | val = emitRead(self, base, fieldInfo.offset, fieldType); |
|
| 3231 | 3277 | } |
|
| 3232 | 3278 | ||
| 3233 | 3279 | let nestedSubject = MatchSubject { |
|
| 3234 | 3280 | val, |
lib/std/lang/resolver.rad
+20 -0
| 3872 | 3872 | Compare, |
|
| 3873 | 3873 | /// Identifier introduces a new binding. |
|
| 3874 | 3874 | Bind, |
|
| 3875 | 3875 | } |
|
| 3876 | 3876 | ||
| 3877 | + | /// Check whether a pattern node is a destructuring pattern that looks |
|
| 3878 | + | /// through structure (union variant, record literal, scope access). |
|
| 3879 | + | /// Identifiers, placeholders, and plain literals are not destructuring. |
|
| 3880 | + | fn isDestructuringPattern(pattern: *ast::Node) -> bool { |
|
| 3881 | + | match pattern.value { |
|
| 3882 | + | case ast::NodeValue::Call(_), |
|
| 3883 | + | ast::NodeValue::RecordLit(_), |
|
| 3884 | + | ast::NodeValue::ScopeAccess(_) => return true, |
|
| 3885 | + | else => return false, |
|
| 3886 | + | } |
|
| 3887 | + | } |
|
| 3888 | + | ||
| 3877 | 3889 | /// Analyze a case pattern for match, if-case, let-case, or while-case. |
|
| 3878 | 3890 | /// |
|
| 3879 | 3891 | /// At the top level, bare identifiers are compared against existing values. |
|
| 3880 | 3892 | /// Inside destructuring patterns (arrays, records), identifiers become bindings. |
|
| 3881 | 3893 | fn resolveCasePattern( |
| 3885 | 3897 | mode: IdentMode, |
|
| 3886 | 3898 | matchBy: MatchBy |
|
| 3887 | 3899 | ) throws (ResolveError) { |
|
| 3888 | 3900 | // TODO: Collapse these nested matches. |
|
| 3889 | 3901 | match scrutineeTy { |
|
| 3902 | + | case Type::Pointer { target, .. } => { |
|
| 3903 | + | // Auto-deref: when the scrutinee is a pointer and the pattern |
|
| 3904 | + | // is a destructuring pattern, resolve against the pointed-to type. |
|
| 3905 | + | if isDestructuringPattern(pattern) { |
|
| 3906 | + | try resolveCasePattern(self, pattern, *target, mode, matchBy); |
|
| 3907 | + | return; |
|
| 3908 | + | } |
|
| 3909 | + | } |
|
| 3890 | 3910 | case Type::Nominal(info) => { |
|
| 3891 | 3911 | try ensureNominalResolved(self, info, pattern); |
|
| 3892 | 3912 | ||
| 3893 | 3913 | match *info { |
|
| 3894 | 3914 | case NominalType::Union(unionType) => { |