Struct eta (lean4lean style): if `s` is a fully-applied constructor of a struct-like type, check `proj(i, t) ≡ s.args[params+i]` for each field. Tries `tryEtaStructCore(t, s)` — caller should try both directions.
(
&mut self,
t: &KExpr<M>,
s: &KExpr<M>,
)
| 1099 | return Ok(*s_val == str_val); |
| 1100 | } |
| 1101 | let expanded = self.str_lit_to_ctor_app(&str_val)?; |
| 1102 | self.is_def_eq(&expanded, s) |
| 1103 | } |
| 1104 | |
| 1105 | /// Convert a string literal to constructor form: |
| 1106 | /// `"abc"` → `String.ofList (List.cons (Char.ofNat 97) (List.cons (Char.ofNat 98) (... List.nil)))` |
| 1107 | /// |
| 1108 | /// Uses `Char.ofNat` (not `Char.mk`) matching lean4lean/C++ kernel. |
| 1109 | /// Uses `String.ofList` (= `String.mk` in our env) matching lean4lean/C++ kernel. |
| 1110 | pub(super) fn str_lit_to_constructor(&mut self, s: &str) -> KExpr<M> { |
| 1111 | let char_const = |
| 1112 | self.intern(KExpr::cnst(self.prims.char_type.clone(), Box::new([]))); |
| 1113 | let char_of_nat = |
| 1114 | self.intern(KExpr::cnst(self.prims.char_of_nat.clone(), Box::new([]))); |
| 1115 | let string_mk = |
| 1116 | self.intern(KExpr::cnst(self.prims.string_of_list.clone(), Box::new([]))); |
| 1117 | |
| 1118 | // List.nil.{0} Char |
| 1119 | let list_nil_z = self.intern(KExpr::cnst( |
| 1120 | self.prims.list_nil.clone(), |
| 1121 | Box::new([KUniv::zero()]), |
| 1122 | )); |
| 1123 | let nil = self.intern(KExpr::app(list_nil_z, char_const.clone())); |
| 1124 | |
| 1125 | // List.cons.{0} Char |
| 1126 | let list_cons_z = self.intern(KExpr::cnst( |
| 1127 | self.prims.list_cons.clone(), |
| 1128 | Box::new([KUniv::zero()]), |
| 1129 | )); |
| 1130 | let cons = self.intern(KExpr::app(list_cons_z, char_const)); |
| 1131 | |
| 1132 | // Build list right-to-left: foldr |
| 1133 | let mut list = nil; |
| 1134 | for c in s.chars().rev() { |
| 1135 | let nat_val = bignat::Nat::from(c as u64); |
| 1136 | let nat_addr = ix_common::address::Address::hash(&nat_val.to_le_bytes()); |
| 1137 | let nat_lit = self.intern(KExpr::nat(nat_val, nat_addr)); |
| 1138 | let char_val = self.intern(KExpr::app(char_of_nat.clone(), nat_lit)); |
| 1139 | let partial = self.intern(KExpr::app(cons.clone(), char_val)); |
| 1140 | list = self.intern(KExpr::app(partial, list)); |
| 1141 | } |
| 1142 | |
| 1143 | // String.mk list |
| 1144 | self.intern(KExpr::app(string_mk, list)) |
| 1145 | } |
| 1146 | |
| 1147 | /// String literal → structure-constructor application. |
| 1148 | /// |
| 1149 | /// `str_lit_to_constructor` produces `String.ofList [Char.ofNat c…]`; |
| 1150 | /// with the native collapse rule in `try_reduce_string`, a whnf of |
| 1151 | /// that application folds straight back into the `Str` literal. So |
| 1152 | /// callers that need the actual constructor form — iota majors, |
| 1153 | /// projections, literal-vs-structural def-eq — take one delta step |
| 1154 | /// past `String.ofList` here: in the compiled environment that |
| 1155 | /// exposes `String.ofByteArray (List.utf8Encode [chars]) proof` |
| 1156 | /// (fields stay lazy). In environments where the `String.ofList` |
| 1157 | /// address IS the constructor (List-Char-model test envs), the delta |
| 1158 | /// step is a no-op and the expansion is already ctor-headed. |
no test coverage detected