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.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.
5import types {
6 CheckResult,
7 PreviewTypedCall,
8 PreviewTypedCapture,
9 PreviewTypedNode,
10 ResolveResult,
11 SyntaxNode,
13import checker_types { TypeValue }
14import checker.model {
15 CheckerRequest,
16 CheckerResponse,
17 CheckerService,
18 CheckerState,
19 ModuleSyntax,
20 constructor,
21 emptyType,
22 functionType,
23 primitive,
25import checker.type_algebra {
26 flattenedHasHole,
27 resolvedFlatType,
28 stringJoin,
29 typeEqual,
30 typeText,
32import checker.syntax_index {
33 addDiagnostic,
34 child,
35 children,
36 moduleSyntax,
37 spanKey,
38 text,
39 unwrapExport,
41import checker.nominals {
42 collectProtocols,
43 fillNominals,
44 predeclareNominals,
46import checker.protocols_methods {
47 collectProtocolImpls,
48 collectReceiverImpls,
49 functionDeclaration,
50 predeclareFunctions,
52import checker.members_capabilities { gateCollectionKey }
53import checker.diagnostics_derives { checkDerives }
54import checker.call_facts {
55 builtinNamespaceObject,
56 diagnoseUnboundReferences,
58import checker.expression_control { inferExpression }
59import checker.statements {
60 checkBlock,
61 checkBlockExpected,
63import checker.bodies_captures {
64 checkFunctionBodies,
65 checkFunctionDeclaration,
66 checkerIndexes,
67 deriveCaptures,
68 diagnoseLispexRuleFactoryEscapes,
69 initialCheckerState,
72function 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 }
144function checkerService() -> CheckerService {
145 dispatchCheckerService
148function 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 ()
164function 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 }
202// The documented command-line entry point is
203// `export function main(args: Array<string>, stdin: string) -> Result<int, string>`,
204// so the entry module's exported `main` has exactly one admitted signature.
205function explicitMainType() -> TypeValue {
206 functionType(
207 [constructor("Array", [primitive("string")]), primitive("string")],
208 [],
209 constructor("Result", [primitive("int"), primitive("string")]),
210 )
213function exportedMainNameNode(module: ModuleSyntax, statement: SyntaxNode) -> Option<SyntaxNode> {
214 if statement.kind != "statement/export" {
215 return None
216 }
217 let declaration = match functionDeclaration(unwrapExport(statement)) {
218 case Some(value) => value
219 case None => return None
220 }
221 let nameNode = match child(declaration, "name") {
222 case Some(value) => value
223 case None => return None
224 }
225 if text(module.source, nameNode) != "main" {
226 return None
227 }
228 Some(nameNode)
231function 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) => index
242 case None => -1
243 }
244 let definition = match state.functions.get(definitionIndex) {
245 case Some(value) => value
246 case None => return ()
247 }
248 let mut defaulted = false
249 for value in definition.parameterDefaults {
250 if value {
251 defaulted = true
252 }
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 )
273// Only the entry module owns the command-line entry point; an exported `main`
274// anywhere else is an ordinary function.
275function diagnoseEntryPointSignatures(state: CheckerState) -> () {
276 for module in state.modules {
277 let entry = match state.resolved.modules.get(module.moduleIndex) {
278 case Some(resolved) => resolved.entry
279 case None => false
280 }
281 if !entry {
282 continue
283 }
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 ()
294function externReplayError(
295 modules: Array<string>,
296 errors: Array<string>,
297 target: string,
298) -> Option<string> {
299 let mut index = 0
300 for module in modules {
301 if module == target {
302 return errors.get(index)
303 }
304 index += 1
305 }
306 None
309function 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) => value
318 case None => return ()
319 }
320 let path = match child(item, "path") {
321 case Some(value) => value
322 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 }
346function 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(())
370// Runs formation and body phases, preserving diagnostic and observation order.
371export 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))

SHA256 e2eaedcdb99f8597eda9bada950c9152cfc8aedc68d5bf8b1bed3e2f65193c7e