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 drive3
// type formation or expression inference.5
import types { SyntaxNode }6
import checker_types { TypeValue }7
import checker.model {8
CheckerState,9
EnumDefinition,10
RecordDefinition,11
constructor,12
literal,13
nominal,14
primitive,15
}16
import checker.type_algebra { widen }17
import checker.syntax_index {18
addDiagnostic,19
child,20
children,21
containsString,22
enumDefinitionIndex,23
recordDefinitionIndex,24
text,25
typeParameterNames,26
unwrapExport,27
}28
import checker.nominals { substitute }29
import checker.protocols_methods {30
appendConformance,31
enumDefinitionForType,32
newtypeDefinitionForType,33
protocolConformanceIndex,34
recordDefinitionForType,35
}36
import checker.members_capabilities {37
capabilityPath,38
jsonCapability,39
nominalCapabilityKey,40
}42
function 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 false59
}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 false73
}74
}75
true76
}77
case None => false78
}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 false89
}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 false104
}105
}106
}107
true108
}109
case None => false110
}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
false121
} 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 => false135
}136
}137
if widened.kind == "primitive" {138
if protocol == "Eq" {139
return true140
}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 true150
}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 false159
}160
for member in widened.members {161
if !deriveTypeCapability(state, moduleIndex, member, protocol, seen) {162
return false163
}164
}165
return true166
}167
if widened.kind == "record" {168
if protocol == "Order" {169
return false170
}171
for field in widened.fields {172
if !deriveTypeCapability(state, moduleIndex, field, protocol, seen) {173
return false174
}175
}176
return true177
}178
if widened.kind == "constructor" {179
if protocol == "Order" {180
return false181
}182
if protocol == "Eq" {183
if widened.name == "Map" || widened.name == "Set" {184
return false185
}186
for argument in widened.arguments {187
if !deriveTypeCapability(state, moduleIndex, argument, protocol, seen) {188
return false189
}190
}191
return true192
}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"205
}207
function 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
""224
}226
function firstEnumDeriveFailure(227
state: CheckerState,228
definition: EnumDefinition,229
protocol: string,230
) -> string {231
for variant in definition.variants {232
let mut index = 0233
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 += 1244
}245
}246
""247
}249
function 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
()283
}285
export 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).length295
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
()376
}SHA256 17945ea7bf5de9f1f8544ddcce963d16e98f5ddc181ea600168d97009c83ad8e