slow.monster

STUDIO HAZE · topaz

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20 · 56 files

compiler/crates/topaz_self_frontend/topaz/emitter/type_test.tpz

Topaz · 12316 B · Original file

1import emitter.ir { Table, typeDeclaration }
2import types { PreviewTypeAtom }
3import emitter.repr { reprAt, isCopyRepr }
4import emitter.rust_text { rustStr }
6export function childTypes(atoms: Array<PreviewTypeAtom>, parent: int) -> Array<int> {
7 let mut out: Array<int> = []
8 let mut index = 0
9 for atom in atoms {
10 if atom.parent == parent { out.push(index) }
11 index += 1
12 }
13 out
16// Semantic atoms preserve alias expansion, structural fields and nominal
17// identities. Tests inspect the value; a container's outer tag is insufficient.
18function typeTestInner(table: Table, scopeId: string, atoms: Array<PreviewTypeAtom>, index: int, access: string, repr: string, active: Map<string, string>) -> Result<string, string> {
19 if index >= atoms.length { return Err("unsupported: missing pattern type") }
20 let atom = atoms[index]
21 let subs = childTypes(atoms, index)
22 if atom.kind == "union" {
23 let mut tests: Array<string> = []
24 for sub in subs { tests.push("({typeTestInner(table, scopeId, atoms, sub, access, repr, active)?})") }
25 return Ok(if tests.length == 0 { "false" } else { tests.join(" || ") })
26 }
27 if atom.kind == "unknown" || atom.kind == "type-parameter" { return Ok("true") }
28 if atom.kind == "rigid" {
29 match runtimeBoundType(table, scopeId, atom.name)? {
30 case Some(body) => return typeTestInner(table, scopeId, body, 0, access, repr, active)
31 case None => return Ok("true")
32 }
33 }
34 if isCopyRepr(repr) {
35 if reprAt(atoms, index) != repr { return Ok("false") }
36 if atom.kind != "literal" { return Ok("true") }
37 return Ok("({access}) == {atom.value}")
38 }
39 let value = "&({access})"
40 if atom.kind == "primitive" || atom.kind == "literal" {
41 let variant = match atom.name {
42 case "int" => "Int"
43 case "float" => "Float"
44 case "bool" => "Bool"
45 case "string" => "Str"
46 case "unit" => return Ok("matches!({value}, Value::Unit)")
47 case "null" => return Ok("matches!({value}, Value::Null)")
48 case "never" => return Ok("false")
49 case _ => return Err("unsupported: pattern type {atom.name}")
50 }
51 if atom.kind == "literal" {
52 let test = if atom.name == "string" { "__v.as_ref() == {rustStr(atom.value)}" } else { "*__v == {atom.value}" }
53 return Ok("matches!({value}, Value::{variant}(__v) if {test})")
54 }
55 return Ok("matches!({value}, Value::{variant}(_))")
56 }
57 if atom.kind == "function" {
58 let mut count = 0
59 let mut variadic = false
60 for sub in subs {
61 if atoms[sub].field == "parameters" { count += 1 }
62 if atoms[sub].field == "variadic" { variadic = true }
63 }
64 return Ok("callable_shape_matches({value}, {count}, {variadic})")
65 }
66 if atom.kind == "record" {
67 let mut checks: Array<string> = []
68 for sub in subs {
69 let test = typeTestInner(table, scopeId, atoms, sub, "(*__v)", "value", active)?
70 checks.push("(match __rec.get({rustStr(atoms[sub].edgeName)}) \{ Some(__v) => {test}, None => false \})")
71 }
72 let rest = if checks.length == 0 { "" } else { " && {checks.join(" && ")}" }
73 return Ok("match {value} \{ Value::Record(__rec) => __rec.len() == {subs.length}{rest}, _ => false \}")
74 }
75 if atom.kind == "constructor" {
76 if atom.name == "Option" && subs.length == 1 {
77 let test = typeTestInner(table, scopeId, atoms, subs[0], "(**__v)", "value", active)?
78 return Ok("match {value} \{ Value::None => true, Value::Some(__v) => {test}, _ => false \}")
79 }
80 if atom.name == "Result" && subs.length == 2 {
81 let yes = typeTestInner(table, scopeId, atoms, subs[0], "(**__v)", "value", active)?
82 let no = typeTestInner(table, scopeId, atoms, subs[1], "(**__v)", "value", active)?
83 return Ok("match {value} \{ Value::Ok(__v) => {yes}, Value::Err(__v) => {no}, _ => false \}")
84 }
85 if (atom.name == "Array" || atom.name == "Set") && subs.length == 1 {
86 let test = typeTestInner(table, scopeId, atoms, subs[0], "(*__v)", "value", active)?
87 let items = if atom.name == "Set" { ".items()" } else { "" }
88 return Ok("match {value} \{ Value::{atom.name}(__xs) => __xs.borrow(){items}.iter().all(|__v| {test}), _ => false \}")
89 }
90 if atom.name == "Map" && subs.length == 2 {
91 let key = typeTestInner(table, scopeId, atoms, subs[0], "__key", "value", active)?
92 let val = typeTestInner(table, scopeId, atoms, subs[1], "__val", "value", active)?
93 return Ok("match {value} \{ Value::Map(__xs) => __xs.borrow().pairs().into_iter().all(|(__key, __val)| ({key}) && ({val})), _ => false \}")
94 }
95 }
96 if atom.kind == "enum" || atom.kind == "nominal-record" || atom.kind == "newtype" {
97 return nominalTest(table, scopeId, atoms, index, access, active)
98 }
99 let variant = match atom.kind {
100 case "json-value" => "Json"
101 case "toml-value" => "Toml"
102 case "url" => "Url"
103 case "match" => "RegexMatch"
104 case "bytes" => "Bytes"
105 case "byte-buffer" => "ByteBuffer"
106 case "path" => "Path"
107 case "regex" => "Regex"
108 case "date" => "Date"
109 case "big-int" => "BigInt"
110 case "decimal" => "Decimal"
111 case "rounding-mode" => return Ok("matches!({value}, Value::Enum \{ enum_id, .. \} if enum_id.as_ref() == \"RoundingMode\")")
112 case _ => ""
113 }
114 if variant != "" { return Ok("matches!({value}, Value::{variant}(_))") }
115 return Err("unsupported: pattern type {atom.kind}/{atom.name}/{atom.identity}")
118export function nominalIdentity(atom: PreviewTypeAtom) -> string {
119 if atom.identity.contains("::") || atom.origin == "" { atom.identity }
120 else { "{atom.origin}::{atom.identity}" }
123export function subtree(atoms: Array<PreviewTypeAtom>, root: int) -> Array<PreviewTypeAtom> {
124 let mut out: Array<PreviewTypeAtom> = []
125 let mut indexes: Map<int, int> = Map.new()
126 let mut index = root
127 while index < atoms.length {
128 let atom = atoms[index]
129 if index > root && atom.parent < root { break }
130 let parent = if index == root { -1 } else { indexes.get(atom.parent) ?? -1 }
131 indexes.insert(index, out.length)
132 out.push(PreviewTypeAtom { ...atom, parent: parent,
133 field: if index == root { "root" } else { atom.field },
134 index: if index == root { 0 } else { atom.index },
135 edgeName: if index == root { "" } else { atom.edgeName } })
136 index += 1
137 }
138 out
141export function substitute(atoms: Array<PreviewTypeAtom>, params: Map<string, Array<PreviewTypeAtom>>) -> Array<PreviewTypeAtom> {
142 let mut out: Array<PreviewTypeAtom> = []
143 let mut indexes: Map<int, int> = Map.new()
144 let mut index = 0
145 for atom in atoms {
146 let parent = if atom.parent < 0 { -1 } else { indexes.get(atom.parent) ?? -1 }
147 indexes.insert(index, out.length)
148 let actual = if atom.kind == "type-parameter" || atom.kind == "rigid" { params.get(atom.name) } else { None }
149 match actual {
150 case Some(types) => {
151 let offset = out.length
152 for t in types {
153 out.push(PreviewTypeAtom { ...t,
154 parent: if t.parent < 0 { parent } else { t.parent + offset },
155 field: if t.parent < 0 { atom.field } else { t.field },
156 index: if t.parent < 0 { atom.index } else { t.index },
157 edgeName: if t.parent < 0 { atom.edgeName } else { t.edgeName } })
158 }
159 }
160 case None => out.push(PreviewTypeAtom { ...atom, parent: parent })
161 }
162 index += 1
163 }
164 out
167function nominalTest(table: Table, scopeId: string, atoms: Array<PreviewTypeAtom>, index: int, access: string,
168 active: Map<string, string>) -> Result<string, string> {
169 let atom = atoms[index]
170 let identity = nominalIdentity(atom)
171 let declaration = match table.nominals.get(identity) {
172 case Some(found) => found
173 case None => return Err("unsupported: nominal type declaration {identity}")
174 }
175 let types = subtree(atoms, index)
176 let key = JSON.stringify(types)?
177 match active.get(key) {
178 case Some(helper) => return Ok("{helper}(&({access}))")
179 case None => ()
180 }
181 let helper = "__nominal_type_{active.length}"
182 let mut next: Map<string, string> = Map.new()
183 for pair in active.entries { next.insert(pair.key, pair.value) }
184 next.insert(key, helper)
185 let mut params: Map<string, Array<PreviewTypeAtom>> = Map.new()
186 let args = childTypes(atoms, index)
187 let mut count = 0
188 for parameter in declaration {
189 if parameter.parent == 0 && parameter.field == "typeParameters" {
190 if count >= args.length { return Err("unsupported: nominal type argument arity {identity}") }
191 params.insert(parameter.name, subtree(atoms, args[count]))
192 count += 1
193 }
194 }
195 if count != args.length { return Err("unsupported: nominal type argument arity {identity}") }
196 if count == 0 {
197 let id = rustStr(identity)
198 if atom.kind == "nominal-record" { return Ok("({access}).is_nominal_record_declaration({id})") }
199 if atom.kind == "newtype" { return Ok("({access}).is_newtype_declaration({id})") }
200 return Ok("matches!(&({access}), Value::Enum \{ enum_id, declaration_identity, .. \} if nominal_declaration_identity(enum_id.as_ref(), declaration_identity.as_deref()) == {id})")
201 }
202 let definition = substitute(declaration, params)
203 let roots = childTypes(definition, 0)
204 let mut checks: Array<string> = []
205 let id = rustStr(identity)
206 let mut body = "false"
207 if atom.kind == "nominal-record" {
208 for field in roots {
209 if definition[field].field != "fields" { continue }
210 let test = typeTestInner(table, scopeId, definition, field, "(*__field)", "value", next)?
211 checks.push("__fields.iter().find(|(n, _)| n.as_ref() == {rustStr(definition[field].edgeName)}).map(|(_, __field)| {test}).unwrap_or(false)")
212 }
213 let fields = if checks.length == 0 { "true" } else { checks.join(" && ") }
214 body = "match __value \{ Value::NominalRecord \{ record_id, declaration_identity, fields: __fields, .. \} if nominal_declaration_identity(record_id.as_ref(), declaration_identity.as_deref()) == {id} => {fields}, _ => false \}"
215 } else if atom.kind == "newtype" {
216 for field in roots {
217 if definition[field].field != "base" { continue }
218 let test = typeTestInner(table, scopeId, definition, field, "(**inner)", "value", next)?
219 body = "match __value \{ Value::Newtype \{ newtype_id, declaration_identity, inner, .. \} if nominal_declaration_identity(newtype_id.as_ref(), declaration_identity.as_deref()) == {id} => {test}, _ => false \}"
220 }
221 ()
222 } else {
223 for variant in roots {
224 if definition[variant].field != "variants" { continue }
225 let payload = childTypes(definition, variant)
226 let mut tests = ["payloads.len() == {payload.length}"]
227 let mut position = 0
228 for field in payload {
229 let test = typeTestInner(table, scopeId, definition, field, "payloads[{position}]", "value", next)?
230 tests.push("({test})")
231 position += 1
232 }
233 checks.push("{rustStr(definition[variant].name)} if {tests.join(" && ")} => true")
234 }
235 body = "match __value \{ Value::Enum \{ enum_id, declaration_identity, variant, payloads, .. \} if nominal_declaration_identity(enum_id.as_ref(), declaration_identity.as_deref()) == {id} => match variant.as_ref() \{ {checks.join(", ")}, _ => false \}, _ => false \}"
236 }
237 Ok("\{ fn {helper}(__value: &Value) -> bool \{ {body} \} {helper}(&({access})) \}")
240export function typeTest(table: Table, scopeId: string, atoms: Array<PreviewTypeAtom>, index: int, access: string, repr: string) -> Result<string, string> {
241 let active: Map<string, string> = Map.new()
242 typeTestInner(table, scopeId, atoms, index, access, repr, active)
245// Named runtime types take precedence over erasing a generic parameter.
246function runtimeBoundType(table: Table, scopeId: string, name: string) -> Result<Option<Array<PreviewTypeAtom>>, string> {
247 let declaration = match typeDeclaration(table, scopeId, name)? {
248 case Some(found) => found
249 case None => return Ok(None)
250 }
251 if declaration.kind != "type-alias" { return Ok(Some([declaration.typeDefinition[0]])) }
252 let mut index = 0
253 for atom in declaration.typeDefinition {
254 if atom.parent == 0 && atom.field == "typeParameters" {
255 return Err("unsupported: generic alias arity {name}")
256 }
257 if atom.parent == 0 && atom.field == "base" {
258 return Ok(Some(subtree(declaration.typeDefinition, index)))
259 }
260 index += 1
261 }
262 Err("unsupported: alias definition {name}")

SHA256 2b42cb0150fa44bec47b987e90f7eb581710b6ee60c92daa4698c86609b432ac