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.tpz
Topaz · 10118 B · Original file
1
// Public type-checking facade for the embedded self compiler.2
// Checker leaves own formation and inference; this file wires their services,3
// runs the phases in order, and exposes only check to the package entry.5
import types {6
CheckResult,7
PreviewTypedCall,8
PreviewTypedCapture,9
PreviewTypedNode,10
ResolveResult,11
SyntaxNode,12
}13
import checker_types { TypeValue }14
import checker.model {15
CheckerRequest,16
CheckerResponse,17
CheckerService,18
CheckerState,19
ModuleSyntax,20
constructor,21
emptyType,22
functionType,23
primitive,24
}25
import checker.type_algebra {26
flattenedHasHole,27
resolvedFlatType,28
stringJoin,29
typeEqual,30
typeText,31
}32
import checker.syntax_index {33
addDiagnostic,34
child,35
children,36
moduleSyntax,37
spanKey,38
text,39
unwrapExport,40
}41
import checker.nominals {42
collectProtocols,43
fillNominals,44
predeclareNominals,45
}46
import checker.protocols_methods {47
collectProtocolImpls,48
collectReceiverImpls,49
functionDeclaration,50
predeclareFunctions,51
}52
import checker.members_capabilities { gateCollectionKey }53
import checker.diagnostics_derives { checkDerives }54
import checker.call_facts {55
builtinNamespaceObject,56
diagnoseUnboundReferences,57
}58
import checker.expression_control { inferExpression }59
import checker.statements {60
checkBlock,61
checkBlockExpected,62
}63
import checker.bodies_captures {64
checkFunctionBodies,65
checkFunctionDeclaration,66
checkerIndexes,67
deriveCaptures,68
diagnoseLispexRuleFactoryEscapes,69
initialCheckerState,70
}72
function dispatchCheckerService(73
state: CheckerState,74
request: CheckerRequest,75
) -> CheckerResponse {76
match request.kind {77
case "check-function" => {78
match state.modules.get(request.moduleIndex) {79
case Some(module) => checkFunctionDeclaration(80
dispatchCheckerService,81
state,82
module,83
request.node,84
)85
case None => ()86
}87
CheckerResponse { typeValue: emptyType("unknown"), boolValue: false }88
}89
case "infer-expression" => CheckerResponse {90
typeValue: inferExpression(91
dispatchCheckerService,92
state,93
request.moduleIndex,94
request.source,95
request.node,96
),97
boolValue: false,98
}99
case "builtin-namespace-object" => CheckerResponse {100
typeValue: emptyType("unknown"),101
boolValue: builtinNamespaceObject(102
dispatchCheckerService,103
state,104
request.moduleIndex,105
request.source,106
request.node,107
),108
}109
case "check-block" => CheckerResponse {110
typeValue: checkBlock(111
dispatchCheckerService,112
state,113
request.moduleIndex,114
request.source,115
request.node,116
),117
boolValue: false,118
}119
case "check-block-expected" => CheckerResponse {120
typeValue: checkBlockExpected(121
dispatchCheckerService,122
state,123
request.moduleIndex,124
request.source,125
request.node,126
request.typeValue,127
),128
boolValue: false,129
}130
case "gate-collection-key" => {131
gateCollectionKey(132
dispatchCheckerService,133
state,134
request.moduleIndex,135
request.node,136
request.typeValue,137
)138
CheckerResponse { typeValue: emptyType("unknown"), boolValue: false }139
}140
case _ => CheckerResponse { typeValue: emptyType("unknown"), boolValue: false }141
}142
}144
function checkerService() -> CheckerService {145
dispatchCheckerService146
}148
function runCheckerPhases(service: CheckerService, state: CheckerState) -> () {149
predeclareNominals(state)150
fillNominals(service, state)151
collectProtocols(service, state)152
collectReceiverImpls(service, state)153
collectProtocolImpls(service, state)154
checkDerives(state)155
predeclareFunctions(service, state)156
diagnoseUnboundReferences(state)157
checkFunctionBodies(service, state)158
diagnoseEntryPointSignatures(state)159
diagnoseLispexRuleFactoryEscapes(state)160
deriveCaptures(state)161
()162
}164
function checkerResult(state: CheckerState) -> CheckResult {165
let mut nodes: Array<PreviewTypedNode> = []166
for node in state.nodes {167
let resolved = resolvedFlatType(node.typeValue, state.inferenceSolutions)168
nodes.push(PreviewTypedNode {169
...node,170
typeValue: resolved,171
ambient: flattenedHasHole(resolved),172
})173
}174
let mut calls: Array<PreviewTypedCall> = []175
for call in state.calls {176
let calleeType = resolvedFlatType(call.calleeType, state.inferenceSolutions)177
let resultType = resolvedFlatType(call.resultType, state.inferenceSolutions)178
calls.push(PreviewTypedCall {179
...call,180
calleeType: calleeType,181
resultType: resultType,182
ambient: flattenedHasHole(calleeType) || flattenedHasHole(resultType),183
})184
}185
let mut captures: Array<PreviewTypedCapture> = []186
for capture in state.captures {187
let resolved = resolvedFlatType(capture.typeValue, state.inferenceSolutions)188
captures.push(PreviewTypedCapture {189
...capture,190
typeValue: resolved,191
ambient: flattenedHasHole(resolved),192
})193
}194
CheckResult {195
nodes: nodes,196
calls: calls,197
captures: captures,198
diagnostics: state.diagnostics,199
}200
}202
// The documented command-line entry point is203
// `export function main(args: Array<string>, stdin: string) -> Result<int, string>`,204
// so the entry module's exported `main` has exactly one admitted signature.205
function explicitMainType() -> TypeValue {206
functionType(207
[constructor("Array", [primitive("string")]), primitive("string")],208
[],209
constructor("Result", [primitive("int"), primitive("string")]),210
)211
}213
function exportedMainNameNode(module: ModuleSyntax, statement: SyntaxNode) -> Option<SyntaxNode> {214
if statement.kind != "statement/export" {215
return None216
}217
let declaration = match functionDeclaration(unwrapExport(statement)) {218
case Some(value) => value219
case None => return None220
}221
let nameNode = match child(declaration, "name") {222
case Some(value) => value223
case None => return None224
}225
if text(module.source, nameNode) != "main" {226
return None227
}228
Some(nameNode)229
}231
function diagnoseExplicitMainSignature(232
state: CheckerState,233
module: ModuleSyntax,234
nameNode: SyntaxNode,235
) -> () {236
let definitionIndex = match state.functionIndexes.get(spanKey(237
module.moduleIndex,238
nameNode.lo,239
nameNode.hi,240
)) {241
case Some(index) => index242
case None => -1243
}244
let definition = match state.functions.get(definitionIndex) {245
case Some(value) => value246
case None => return ()247
}248
let mut defaulted = false249
for value in definition.parameterDefaults {250
if value {251
defaulted = true252
}253
}254
let expected = explicitMainType()255
let actual = functionType(256
definition.parameterTypes,257
definition.variadic,258
definition.result,259
)260
if definition.typeParameters.length == 0 && !defaulted && typeEqual(actual, expected) {261
return ()262
}263
addDiagnostic(264
state,265
module.moduleIndex,266
"TPZ5022",267
"`export function main` must be non-generic and have signature `{typeText(expected)}`; found `{typeText(actual)}`",268
"",269
nameNode,270
)271
}273
// Only the entry module owns the command-line entry point; an exported `main`274
// anywhere else is an ordinary function.275
function diagnoseEntryPointSignatures(state: CheckerState) -> () {276
for module in state.modules {277
let entry = match state.resolved.modules.get(module.moduleIndex) {278
case Some(resolved) => resolved.entry279
case None => false280
}281
if !entry {282
continue283
}284
for statement in module.root.children {285
match exportedMainNameNode(module, statement) {286
case Some(nameNode) => diagnoseExplicitMainSignature(state, module, nameNode)287
case None => ()288
}289
}290
}291
()292
}294
function externReplayError(295
modules: Array<string>,296
errors: Array<string>,297
target: string,298
) -> Option<string> {299
let mut index = 0300
for module in modules {301
if module == target {302
return errors.get(index)303
}304
index += 1305
}306
None307
}309
function diagnoseExternReplayImport(310
state: CheckerState,311
module: ModuleSyntax,312
statement: SyntaxNode,313
externReplayModules: Array<string>,314
externReplayErrors: Array<string>,315
) -> () {316
let item = match child(statement, "import") {317
case Some(value) => value318
case None => return ()319
}320
let path = match child(item, "path") {321
case Some(value) => value322
case None => return ()323
}324
let mut segments: Array<string> = []325
for segment in children(path, "segments") {326
segments.push(text(module.source, segment))327
}328
let target = stringJoin(segments, ".")329
match externReplayError(330
externReplayModules,331
externReplayErrors,332
target,333
) {334
case Some(error) => addDiagnostic(335
state,336
module.moduleIndex,337
"TPZ5032",338
"extern module `{target}` has an invalid deterministic replay binding: {error}",339
"",340
path,341
)342
case None => ()343
}344
}346
function diagnoseExternReplayErrors(347
state: CheckerState,348
externReplayModules: Array<string>,349
externReplayErrors: Array<string>,350
) -> Result<(), string> {351
if externReplayModules.length != externReplayErrors.length {352
return Err("checker extern replay fact columns differ in length")353
}354
for module in state.modules {355
for statement in module.root.children {356
if statement.kind == "statement/import" {357
diagnoseExternReplayImport(358
state,359
module,360
statement,361
externReplayModules,362
externReplayErrors,363
)364
}365
}366
}367
Ok(())368
}370
// Runs formation and body phases, preserving diagnostic and observation order.371
export function check(372
resolved: ResolveResult,373
externReplayModules: Array<string>,374
externReplayErrors: Array<string>,375
maxAstNodes: int,376
maxAstDepth: int,377
) -> Result<CheckResult, string> {378
let service = checkerService()379
let modules = moduleSyntax(resolved, maxAstNodes, maxAstDepth)?380
let state = initialCheckerState(resolved, modules, checkerIndexes(resolved))381
diagnoseExternReplayErrors(state, externReplayModules, externReplayErrors)?382
runCheckerPhases(service, state)383
Ok(checkerResult(state))384
}SHA256 e2eaedcdb99f8597eda9bada950c9152cfc8aedc68d5bf8b1bed3e2f65193c7e