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/checker/diagnostics_derives.tpz

Topaz · 10200 B · Original file

1// Diagnostic helpers and validation of declared derive capabilities.
2// The leaf appends stable checker diagnostics to shared state but does not drive
3// type formation or expression inference.
5import types { SyntaxNode }
6import checker_types { TypeValue }
7import checker.model {
8 CheckerState,
9 EnumDefinition,
10 RecordDefinition,
11 constructor,
12 literal,
13 nominal,
14 primitive,
16import checker.type_algebra { widen }
17import checker.syntax_index {
18 addDiagnostic,
19 child,
20 children,
21 containsString,
22 enumDefinitionIndex,
23 recordDefinitionIndex,
24 text,
25 typeParameterNames,
26 unwrapExport,
28import checker.nominals { substitute }
29import checker.protocols_methods {
30 appendConformance,
31 enumDefinitionForType,
32 newtypeDefinitionForType,
33 protocolConformanceIndex,
34 recordDefinitionForType,
36import checker.members_capabilities {
37 capabilityPath,
38 jsonCapability,
39 nominalCapabilityKey,
42function deriveTypeCapability(
43 state: CheckerState,
44 moduleIndex: int,
45 value: TypeValue,
46 protocol: string,
47 seen: Array<string>,
48) -> bool {
49 let widened = widen(value)
50 if widened.kind == "nominal-record" {
51 let key = nominalCapabilityKey(widened)
52 if containsString(seen, key) {
53 return protocol != "JSON"
54 }
55 return match recordDefinitionForType(state, moduleIndex, widened) {
56 case Some(definition) => {
57 if definition.typeParameters.length != widened.arguments.length {
58 return false
59 }
60 for field in definition.fields {
61 if !deriveTypeCapability(
62 state,
63 definition.moduleIndex,
64 substitute(
65 field.typeValue,
66 definition.typeParameters,
67 widened.arguments,
68 ),
69 protocol,
70 capabilityPath(seen, key),
71 ) {
72 return false
73 }
74 }
75 true
76 }
77 case None => false
78 }
79 }
80 if widened.kind == "enum" {
81 let key = nominalCapabilityKey(widened)
82 if containsString(seen, key) {
83 return protocol != "JSON"
84 }
85 return match enumDefinitionForType(state, moduleIndex, widened) {
86 case Some(definition) => {
87 if definition.typeParameters.length != widened.arguments.length {
88 return false
89 }
90 for variant in definition.variants {
91 for payload in variant.payloads {
92 if !deriveTypeCapability(
93 state,
94 definition.moduleIndex,
95 substitute(
96 payload,
97 definition.typeParameters,
98 widened.arguments,
99 ),
100 protocol,
101 capabilityPath(seen, key),
102 ) {
103 return false
104 }
105 }
106 }
107 true
108 }
109 case None => false
110 }
111 }
112 if widened.kind == "newtype" {
113 let key = nominalCapabilityKey(widened)
114 if containsString(seen, key) {
115 return protocol != "JSON"
116 }
117 return match newtypeDefinitionForType(state, moduleIndex, widened) {
118 case Some(definition) => if definition.typeParameters.length !=
119 widened.arguments.length {
120 false
121 } else {
122 deriveTypeCapability(
123 state,
124 definition.moduleIndex,
125 substitute(
126 definition.base,
127 definition.typeParameters,
128 widened.arguments,
129 ),
130 protocol,
131 capabilityPath(seen, key),
132 )
133 }
134 case None => false
135 }
136 }
137 if widened.kind == "primitive" {
138 if protocol == "Eq" {
139 return true
140 }
141 if protocol == "Order" {
142 return widened.name == "int" || widened.name == "string"
143 }
144 return widened.name == "int" || widened.name == "string" ||
145 widened.name == "bool" || widened.name == "unit"
146 }
147 if widened.kind == "literal" {
148 if protocol == "Eq" {
149 return true
150 }
151 if protocol == "Order" {
152 return widened.name == "int" || widened.name == "string"
153 }
154 return widened.name != "float"
155 }
156 if widened.kind == "union" {
157 if protocol != "Eq" {
158 return false
159 }
160 for member in widened.members {
161 if !deriveTypeCapability(state, moduleIndex, member, protocol, seen) {
162 return false
163 }
164 }
165 return true
166 }
167 if widened.kind == "record" {
168 if protocol == "Order" {
169 return false
170 }
171 for field in widened.fields {
172 if !deriveTypeCapability(state, moduleIndex, field, protocol, seen) {
173 return false
174 }
175 }
176 return true
177 }
178 if widened.kind == "constructor" {
179 if protocol == "Order" {
180 return false
181 }
182 if protocol == "Eq" {
183 if widened.name == "Map" || widened.name == "Set" {
184 return false
185 }
186 for argument in widened.arguments {
187 if !deriveTypeCapability(state, moduleIndex, argument, protocol, seen) {
188 return false
189 }
190 }
191 return true
192 }
193 return jsonCapability(state, moduleIndex, widened, seen, false) == "accept" &&
194 jsonCapability(state, moduleIndex, widened, seen, true) == "accept"
195 }
196 if widened.kind == "bytes" || widened.kind == "path" ||
197 widened.kind == "url" || widened.kind == "date" ||
198 widened.kind == "big-int" || widened.kind == "decimal" {
199 return protocol == "Eq" || protocol == "Order"
200 }
201 if widened.kind == "match" || widened.kind == "rounding-mode" {
202 return protocol == "Eq"
203 }
204 widened.kind == "json-value" && protocol == "JSON"
207function firstRecordDeriveFailure(
208 state: CheckerState,
209 definition: RecordDefinition,
210 protocol: string,
211) -> string {
212 for field in definition.fields {
213 if !deriveTypeCapability(
214 state,
215 definition.moduleIndex,
216 field.typeValue,
217 protocol,
218 [],
219 ) {
220 return "field `{field.name}`"
221 }
222 }
223 ""
226function firstEnumDeriveFailure(
227 state: CheckerState,
228 definition: EnumDefinition,
229 protocol: string,
230) -> string {
231 for variant in definition.variants {
232 let mut index = 0
233 for payload in variant.payloads {
234 if !deriveTypeCapability(
235 state,
236 definition.moduleIndex,
237 payload,
238 protocol,
239 [],
240 ) {
241 return "variant `{variant.name}` payload #{index}"
242 }
243 index += 1
244 }
245 }
246 ""
249function recordDerivedConformance(
250 state: CheckerState,
251 moduleIndex: int,
252 typeName: string,
253 protocol: string,
254 node: SyntaxNode,
255) -> () {
256 let existingIndex = protocolConformanceIndex(
257 state,
258 moduleIndex,
259 typeName,
260 protocol,
261 )
262 if existingIndex >= 0 {
263 let how = match state.conformances.get(existingIndex) {
264 case Some(existing) => if existing.derived {
265 "is derived more than once"
266 } else {
267 "is already implemented manually"
268 }
269 case None => "is derived more than once"
270 }
271 addDiagnostic(
272 state,
273 moduleIndex,
274 "TPZ5521",
275 "`{typeName}` conformance to `{protocol}` {how}; a conformance must be unique",
276 "",
277 node,
278 )
279 } else {
280 appendConformance(state, moduleIndex, typeName, protocol, true)
281 }
282 ()
285export function checkDerives(state: CheckerState) -> () {
286 for module in state.modules {
287 for statement in module.root.children {
288 let item = unwrapExport(statement)
289 if item.kind == "statement/record" || item.kind == "statement/enum" {
290 let name = match child(item, "name") {
291 case Some(found) => text(module.source, found)
292 case None => ""
293 }
294 let typeParameterCount = typeParameterNames(module.source, item).length
295 for derived in children(item, "derives") {
296 let protocol = text(module.source, derived)
297 if protocol != "Eq" && protocol != "Order" &&
298 protocol != "Show" && protocol != "JSON" {
299 addDiagnostic(
300 state,
301 module.moduleIndex,
302 "TPZ5530",
303 "unknown protocol `{protocol}` in `derives` for `{name}`; derivable protocols are `Eq`, `Order`, `Show`, `JSON`",
304 "",
305 derived,
306 )
307 } else if typeParameterCount > 0 {
308 addDiagnostic(
309 state,
310 module.moduleIndex,
311 "TPZ5022",
312 "cannot derive `{protocol}` for generic nominal `{name}` yet; conditional generic conformance is not defined",
313 "",
314 derived,
315 )
316 } else {
317 let failure = if protocol == "Show" {
318 ""
319 } else if item.kind == "statement/record" {
320 match state.records.get(recordDefinitionIndex(
321 state,
322 module.moduleIndex,
323 name,
324 )) {
325 case Some(definition) => firstRecordDeriveFailure(
326 state,
327 definition,
328 protocol,
329 )
330 case None => "nominal"
331 }
332 } else {
333 match state.enums.get(enumDefinitionIndex(
334 state,
335 module.moduleIndex,
336 name,
337 )) {
338 case Some(definition) => firstEnumDeriveFailure(
339 state,
340 definition,
341 protocol,
342 )
343 case None => "nominal"
344 }
345 }
346 if failure == "" {
347 recordDerivedConformance(
348 state,
349 module.moduleIndex,
350 name,
351 protocol,
352 derived,
353 )
354 } else {
355 addDiagnostic(
356 state,
357 module.moduleIndex,
358 "TPZ5530",
359 if protocol == "Eq" {
360 "cannot derive `Eq` for `{name}`: {failure} has a non-comparable type"
361 } else if protocol == "Order" {
362 "cannot derive `Order` for `{name}`: {failure} is not totally orderable"
363 } else {
364 "cannot derive `JSON` for `{name}`: {failure} cannot round-trip through JSON"
365 },
366 "",
367 derived,
368 )
369 }
370 }
371 }
372 }
373 }
374 }
375 ()

SHA256 17945ea7bf5de9f1f8544ddcce963d16e98f5ddc181ea600168d97009c83ad8e