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/resolver/validation_init.tpz

Topaz · 23320 B · Original file

1// Public type-surface checks and top-level initialization analysis.
2// It consumes completed module and scope catalogs, then assembles accepted or
3// rejected resolver outputs for the facade.
5import types {
6 PreviewImportEdge,
7 PreviewModuleResult,
8 PreviewResolvedDeclaration,
9 PreviewResolvedDiagnostic,
10 PreviewResolvedExport,
11 PreviewResolvedReference,
12 PreviewResolvedScope,
13 ResolveResult,
14 SyntaxNode,
16import unicode { didYouMean }
17import resolver.model_facts {
18 InitViolation,
19 LocalScope,
20 ModuleDraft,
21 ModuleTable,
22 ModuleTableCatalog,
23 ResolverOutputs,
24 ResolverWalkContext,
25 attribute,
26 child,
27 children,
28 diagnostic,
29 isNone,
30 spanText,
31 stringJoin,
32 text,
34import resolver.modules_tables {
35 catalogTable,
36 effectiveNamespace,
37 exportedBinding,
38 exportedNames,
39 tableBinding,
40 unwrapExport,
42import resolver.scopes_walk {
43 addDeclaration,
44 appendModuleExports,
45 walkNode,
48function privatePublicSurfaceTypeBinding(
49 table: ModuleTable,
50 name: string,
51) -> bool {
52 let binding = match tableBinding(table, name) {
53 case Some(found) => found
54 case None => return false
55 }
56 binding.namespace == "type" &&
57 !binding.exported &&
58 (binding.declarationKind == "type-alias" ||
59 binding.declarationKind == "nominal-type")
62function validatePublicTypeTree(
63 source: Bytes,
64 nodeValue: SyntaxNode,
65 table: ModuleTable,
66 diagnostics: Array<PreviewResolvedDiagnostic>,
67) -> () {
68 if nodeValue.kind == "type/named" {
69 match child(nodeValue, "name") {
70 case Some(name) => {
71 let nameText = text(source, name)
72 if privatePublicSurfaceTypeBinding(table, nameText) {
73 diagnostic(
74 diagnostics,
75 table.moduleIndex,
76 "TPZ3014",
77 "`{nameText}` is a module-private type and may not appear in an exported public surface; export the type or use an inline structural type",
78 name.lo,
79 name.hi,
80 )
81 }
82 }
83 case None => ()
84 }
85 }
86 for found in nodeValue.children {
87 validatePublicTypeTree(source, found, table, diagnostics)
88 }
89 ()
92function validateExportSignature(
93 source: Bytes,
94 statement: SyntaxNode,
95 table: ModuleTable,
96 diagnostics: Array<PreviewResolvedDiagnostic>,
97) -> () {
98 if statement.kind != "statement/export" {
99 return ()
100 }
101 let item = unwrapExport(statement)
102 if item.kind == "statement/function" {
103 match child(item, "declaration") {
104 case Some(declaration) => {
105 for parameter in children(declaration, "parameters") {
106 match child(parameter, "type") {
107 case Some(typeNode) =>
108 validatePublicTypeTree(source, typeNode, table, diagnostics)
109 case None => ()
110 }
111 }
112 match child(declaration, "returnType") {
113 case Some(typeNode) =>
114 validatePublicTypeTree(source, typeNode, table, diagnostics)
115 case None => ()
116 }
117 }
118 case None => ()
119 }
120 return ()
121 }
122 if item.kind == "statement/type-alias" {
123 match child(item, "type") {
124 case Some(typeNode) => validatePublicTypeTree(source, typeNode, table, diagnostics)
125 case None => ()
126 }
127 return ()
128 }
129 if item.kind == "statement/enum" {
130 for variant in children(item, "variants") {
131 for typeNode in children(variant, "payload") {
132 validatePublicTypeTree(source, typeNode, table, diagnostics)
133 }
134 }
135 return ()
136 }
137 if item.kind == "statement/record" {
138 for field in children(item, "fields") {
139 match child(field, "type") {
140 case Some(typeNode) => validatePublicTypeTree(source, typeNode, table, diagnostics)
141 case None => ()
142 }
143 }
144 return ()
145 }
146 if item.kind == "statement/newtype" {
147 match child(item, "base") {
148 case Some(typeNode) => validatePublicTypeTree(source, typeNode, table, diagnostics)
149 case None => ()
150 }
151 return ()
152 }
153 if item.kind == "statement/let" {
154 match child(item, "type") {
155 case Some(typeNode) => validatePublicTypeTree(source, typeNode, table, diagnostics)
156 case None => ()
157 }
158 }
159 ()
162function validateExportSignatures(
163 module: ModuleDraft,
164 table: ModuleTable,
165 diagnostics: Array<PreviewResolvedDiagnostic>,
166) -> () {
167 let source = module.source
168 for statement in module.root.children {
169 validateExportSignature(source, statement, table, diagnostics)
170 }
171 ()
174export function validateImportSurfaces(
175 modules: Array<ModuleDraft>,
176 tables: ModuleTableCatalog,
177 diagnostics: Array<PreviewResolvedDiagnostic>,
178) -> () {
179 let mut importingIndex = 0
180 for module in modules {
181 for import in module.imports {
182 match catalogTable(tables, import.target) {
183 case Some(targetTable) => if targetTable.exportedNames.length == 0 {
184 diagnostic(
185 diagnostics,
186 importingIndex,
187 "TPZ3010",
188 "`{import.target}` exports nothing; v5.2 has no side-effect-only imports",
189 import.pathLo,
190 import.pathHi,
191 )
192 } else {
193 let mut selectedIndex = 0
194 for selected in import.selectedNames {
195 if isNone(exportedBinding(targetTable, selected)) {
196 let hint = didYouMean(
197 selected,
198 exportedNames(targetTable, ""),
199 )
200 diagnostic(
201 diagnostics,
202 importingIndex,
203 "TPZ3009",
204 "`{selected}` is not exported by `{import.target}`{hint}",
205 import.selectedLos[selectedIndex],
206 import.selectedHis[selectedIndex],
207 )
208 }
209 selectedIndex += 1
210 }
211 }
212 case None => ()
213 }
214 }
215 validateExportSignatures(module, tables.tables[importingIndex], diagnostics)
216 importingIndex += 1
217 }
218 ()
221function resolveModuleFacts(
222 module: ModuleDraft,
223 index: int,
224 tables: ModuleTableCatalog,
225 outputs: ResolverOutputs,
226 diagnostics: Array<PreviewResolvedDiagnostic>,
227) -> () {
228 let mut moduleResults = outputs.modules
229 moduleResults.push(module.result)
230 let mut scopes = outputs.scopes
231 scopes.push(PreviewResolvedScope {
232 moduleIndex: index,
233 ordinal: 0,
234 parentOrdinal: -1,
235 kind: "module",
236 lo: module.root.lo,
237 hi: module.root.hi,
238 })
239 let table = tables.tables[index]
240 for binding in table.bindings {
241 if binding.declarationKind != "protocol" {
242 addDeclaration(
243 outputs.declarations,
244 binding,
245 effectiveNamespace(binding, tables),
246 !isNone(exportedBinding(table, binding.name)),
247 )
248 }
249 }
250 appendModuleExports(outputs.exports, index, table)
251 let mut localScopes: Array<LocalScope> = []
252 let mut nextScope: Array<int> = [1]
253 let walkContext = ResolverWalkContext {
254 moduleIndex: index,
255 source: module.source,
256 table: table,
257 tables: tables,
258 nextScope: nextScope,
259 scopeOutput: outputs.scopes,
260 declarationOutput: outputs.declarations,
261 referenceOutput: outputs.references,
262 diagnostics: diagnostics,
263 recordDefaultStack: [],
264 }
265 for statement in module.root.children {
266 let item = unwrapExport(statement)
267 if item.kind != "statement/import" {
268 walkNode(walkContext, item, localScopes)
269 }
270 }
271 ()
274function initPatternNames(
275 pattern: SyntaxNode,
276 source: Bytes,
277 output: Array<string>,
278) -> () {
279 if pattern.kind == "pattern/binding" || pattern.kind == "pattern/typed" {
280 match child(pattern, "name") {
281 case Some(name) => {
282 let mut names = output
283 names.push(text(source, name))
284 }
285 case None => ()
286 }
287 return ()
288 }
289 if pattern.kind == "pattern/or" {
290 for alternative in children(pattern, "alternatives") {
291 initPatternNames(alternative, source, output)
292 }
293 return ()
294 }
295 if pattern.kind == "pattern/constructor" {
296 for argument in children(pattern, "arguments") {
297 initPatternNames(argument, source, output)
298 }
299 return ()
300 }
301 if pattern.kind == "pattern/list" {
302 for element in children(pattern, "elements") {
303 if element.kind == "list-pattern/rest" {
304 match element.children.get(0) {
305 case Some(binding) => initPatternNames(binding, source, output)
306 case None => ()
307 }
308 } else {
309 initPatternNames(element, source, output)
310 }
311 }
312 return ()
313 }
314 if pattern.kind == "pattern/record" ||
315 pattern.kind == "pattern/nominal-record" {
316 for field in children(pattern, "fields") {
317 match child(field, "pattern") {
318 case Some(found) => initPatternNames(found, source, output)
319 case None => match child(field, "name") {
320 case Some(name) => {
321 let mut names = output
322 names.push(text(source, name))
323 }
324 case None => ()
325 }
326 }
327 }
328 }
329 ()
332function initItemNames(item: SyntaxNode, source: Bytes) -> Array<string> {
333 let mut output: Array<string> = []
334 if item.kind == "statement/let" {
335 match child(item, "pattern") {
336 case Some(pattern) => initPatternNames(pattern, source, output)
337 case None => ()
338 }
339 } else if item.kind == "statement/function" {
340 match child(item, "declaration") {
341 case Some(declaration) => match child(declaration, "name") {
342 case Some(name) => output.push(text(source, name))
343 case None => ()
344 }
345 case None => ()
346 }
347 }
348 output
351function initLocalContains(locals: Array<string>, name: string) -> bool {
352 for local in locals {
353 if local == name {
354 return true
355 }
356 }
357 false
360function initObserveReference(
361 node: SyntaxNode,
362 source: Bytes,
363 table: ModuleTable,
364 itemLo: int,
365 locals: Array<string>,
366 violation: Array<InitViolation>,
367) -> () {
368 if violation.length > 0 {
369 return ()
370 }
371 let name = text(source, node)
372 if initLocalContains(locals, name) {
373 return ()
374 }
375 // ModuleTable already owns every unique top-level binding. Since item spans
376 // are ordered and non-overlapping, declaration byte >= current item byte is
377 // the self-host form of Stage 0's textual binding index >= item index rule.
378 let binding = match tableBinding(table, name) {
379 case Some(found) => found
380 case None => return ()
381 }
382 if (binding.declarationKind == "let" ||
383 binding.declarationKind == "function") && binding.lo >= itemLo {
384 let mut found = violation
385 found.push(InitViolation { name: name, lo: node.lo, hi: node.hi })
386 }
387 ()
390function initScanChild(
391 owner: SyntaxNode,
392 field: string,
393 source: Bytes,
394 table: ModuleTable,
395 itemLo: int,
396 locals: Array<string>,
397 violation: Array<InitViolation>,
398) -> () {
399 match child(owner, field) {
400 case Some(found) => initScanExpression(
401 found,
402 source,
403 table,
404 itemLo,
405 locals,
406 violation,
407 )
408 case None => ()
409 }
410 ()
413function initScanTwoChildren(
414 owner: SyntaxNode,
415 first: string,
416 second: string,
417 source: Bytes,
418 table: ModuleTable,
419 itemLo: int,
420 locals: Array<string>,
421 violation: Array<InitViolation>,
422) -> () {
423 initScanChild(owner, first, source, table, itemLo, locals, violation)
424 initScanChild(owner, second, source, table, itemLo, locals, violation)
425 ()
428function initScanThreeChildren(
429 owner: SyntaxNode,
430 first: string,
431 second: string,
432 third: string,
433 source: Bytes,
434 table: ModuleTable,
435 itemLo: int,
436 locals: Array<string>,
437 violation: Array<InitViolation>,
438) -> () {
439 initScanTwoChildren(
440 owner,
441 first,
442 second,
443 source,
444 table,
445 itemLo,
446 locals,
447 violation,
448 )
449 initScanChild(owner, third, source, table, itemLo, locals, violation)
450 ()
453function initScanExpression(
454 expression: SyntaxNode,
455 source: Bytes,
456 table: ModuleTable,
457 itemLo: int,
458 locals: Array<string>,
459 violation: Array<InitViolation>,
460) -> () {
461 if expression.kind == "expression/identifier" {
462 return initObserveReference(
463 expression,
464 source,
465 table,
466 itemLo,
467 locals,
468 violation,
469 )
470 }
471 if expression.kind == "expression/call" {
472 let mut argumentsImmediate = true
473 match child(expression, "callee") {
474 case Some(callee) => {
475 if callee.kind == "expression/optional-access" {
476 argumentsImmediate = false
477 }
478 if callee.kind == "expression/identifier" {
479 initObserveReference(callee, source, table, itemLo, locals, violation)
480 } else if callee.kind != "expression/lambda" {
481 initScanExpression(callee, source, table, itemLo, locals, violation)
482 }
483 }
484 case None => ()
485 }
486 if argumentsImmediate {
487 for argument in children(expression, "arguments") {
488 if argument.kind == "call-argument/spread" ||
489 argument.kind == "call-argument/named" {
490 initScanChild(argument, "value", source, table, itemLo, locals, violation)
491 } else {
492 initScanExpression(argument, source, table, itemLo, locals, violation)
493 }
494 }
495 }
496 return ()
497 }
498 if expression.kind == "expression/lambda" {
499 return ()
500 }
501 if expression.kind == "expression/block" {
502 match child(expression, "block") {
503 case Some(block) => initScanBlock(block, source, table, itemLo, locals, violation)
504 case None => ()
505 }
506 return ()
507 }
508 let immediateField = match expression.kind {
509 case "expression/if" => "condition"
510 case "expression/match" => "scrutinee"
511 case "expression/for" => "iterator"
512 case "expression/concurrent" => "timeout"
513 case "expression/unary" | "expression/try" => "operand"
514 case "expression/member" | "expression/optional-access" => "object"
515 case _ => ""
516 }
517 if immediateField != "" {
518 initScanChild(
519 expression,
520 immediateField,
521 source,
522 table,
523 itemLo,
524 locals,
525 violation,
526 )
527 return ()
528 }
529 if expression.kind == "expression/comprehension" {
530 match children(expression, "clauses").get(0) {
531 case Some(clause) => if clause.kind == "comprehension-clause/for" {
532 initScanChild(clause, "iterator", source, table, itemLo, locals, violation)
533 }
534 case None => ()
535 }
536 return ()
537 }
538 if expression.kind == "expression/parenthesized" {
539 match expression.children.get(0) {
540 case Some(inner) => initScanExpression(
541 inner,
542 source,
543 table,
544 itemLo,
545 locals,
546 violation,
547 )
548 case None => ()
549 }
550 return ()
551 }
552 if expression.kind == "expression/index" {
553 initScanTwoChildren(
554 expression,
555 "object",
556 "index",
557 source,
558 table,
559 itemLo,
560 locals,
561 violation,
562 )
563 return ()
564 }
565 if expression.kind == "expression/binary" {
566 initScanChild(expression, "left", source, table, itemLo, locals, violation)
567 let operator = attribute(expression, "operator")
568 if operator != "and" && operator != "or" && operator != "coalesce" {
569 initScanChild(expression, "right", source, table, itemLo, locals, violation)
570 }
571 return ()
572 }
573 if expression.kind == "expression/compose" {
574 initScanTwoChildren(
575 expression,
576 "left",
577 "right",
578 source,
579 table,
580 itemLo,
581 locals,
582 violation,
583 )
584 return ()
585 }
586 if expression.kind == "expression/range" {
587 initScanThreeChildren(
588 expression,
589 "low",
590 "high",
591 "step",
592 source,
593 table,
594 itemLo,
595 locals,
596 violation,
597 )
598 return ()
599 }
600 if expression.kind == "expression/pipe" {
601 initScanTwoChildren(
602 expression,
603 "left",
604 "right",
605 source,
606 table,
607 itemLo,
608 locals,
609 violation,
610 )
611 return ()
612 }
613 if expression.kind == "expression/record-update" {
614 initScanTwoChildren(
615 expression,
616 "base",
617 "spread",
618 source,
619 table,
620 itemLo,
621 locals,
622 violation,
623 )
624 }
625 if expression.kind == "expression/record-literal" ||
626 expression.kind == "expression/record-update" {
627 for field in children(expression, "fields") {
628 initScanChild(field, "value", source, table, itemLo, locals, violation)
629 }
630 return ()
631 }
632 if expression.kind == "expression/array" || expression.kind == "expression/set" {
633 for element in children(expression, "elements") {
634 if element.kind == "array-element/spread" {
635 initScanChild(element, "value", source, table, itemLo, locals, violation)
636 } else {
637 initScanExpression(element, source, table, itemLo, locals, violation)
638 }
639 }
640 return ()
641 }
642 if expression.kind == "expression/map" {
643 for entry in children(expression, "entries") {
644 initScanTwoChildren(
645 entry,
646 "key",
647 "value",
648 source,
649 table,
650 itemLo,
651 locals,
652 violation,
653 )
654 }
655 return ()
656 }
657 if expression.kind == "expression/string" || expression.kind == "expression/template" {
658 for part in children(expression, "parts") {
659 if part.kind == "string-part/interpolation" {
660 initScanChild(part, "value", source, table, itemLo, locals, violation)
661 }
662 }
663 }
664 ()
667function initBindPattern(
668 pattern: SyntaxNode,
669 source: Bytes,
670 locals: Array<string>,
671) -> () {
672 let mut bound: Array<string> = []
673 initPatternNames(pattern, source, bound)
674 let mut active = locals
675 for name in bound {
676 active.push(name)
677 }
678 ()
681function initScanBlock(
682 block: SyntaxNode,
683 source: Bytes,
684 table: ModuleTable,
685 itemLo: int,
686 locals: Array<string>,
687 violation: Array<InitViolation>,
688) -> () {
689 let priorLocalCount = locals.length
690 let mut active = locals
691 for statement in children(block, "statements") {
692 if statement.kind == "statement/function" {
693 match child(statement, "declaration") {
694 case Some(declaration) => match child(declaration, "name") {
695 case Some(name) => active.push(text(source, name))
696 case None => ()
697 }
698 case None => ()
699 }
700 } else if statement.kind == "statement/let" {
701 initScanChild(statement, "value", source, table, itemLo, active, violation)
702 match child(statement, "pattern") {
703 case Some(pattern) => initBindPattern(pattern, source, active)
704 case None => ()
705 }
706 } else if statement.kind == "statement/const" {
707 initScanChild(statement, "value", source, table, itemLo, active, violation)
708 match child(statement, "name") {
709 case Some(name) => active.push(text(source, name))
710 case None => ()
711 }
712 } else if statement.kind == "statement/using" {
713 initScanChild(statement, "value", source, table, itemLo, active, violation)
714 let priorUsingLocalCount = active.length
715 match child(statement, "name") {
716 case Some(name) => active.push(text(source, name))
717 case None => ()
718 }
719 match child(statement, "body") {
720 case Some(body) => initScanBlock(
721 body,
722 source,
723 table,
724 itemLo,
725 active,
726 violation,
727 )
728 case None => ()
729 }
730 while active.length > priorUsingLocalCount {
731 active.pop()
732 }
733 } else if statement.kind == "statement/assign" {
734 match child(statement, "target") {
735 case Some(target) => if target.kind == "expression/identifier" {
736 initObserveReference(target, source, table, itemLo, active, violation)
737 } else {
738 initScanExpression(target, source, table, itemLo, active, violation)
739 }
740 case None => ()
741 }
742 initScanChild(statement, "value", source, table, itemLo, active, violation)
743 } else if statement.kind == "statement/return" ||
744 statement.kind == "statement/expression" {
745 initScanChild(statement, "value", source, table, itemLo, active, violation)
746 } else if statement.kind == "statement/while" {
747 initScanChild(statement, "condition", source, table, itemLo, active, violation)
748 }
749 }
750 initScanChild(block, "tail", source, table, itemLo, active, violation)
751 while active.length > priorLocalCount {
752 active.pop()
753 }
754 ()
757function initScanItem(
758 item: SyntaxNode,
759 source: Bytes,
760 table: ModuleTable,
761 violation: Array<InitViolation>,
762) -> () {
763 let mut locals: Array<string> = []
764 if item.kind == "statement/let" || item.kind == "statement/expression" ||
765 item.kind == "statement/return" {
766 initScanChild(item, "value", source, table, item.lo, locals, violation)
767 } else if item.kind == "statement/assign" {
768 initScanTwoChildren(
769 item,
770 "target",
771 "value",
772 source,
773 table,
774 item.lo,
775 locals,
776 violation,
777 )
778 } else if item.kind == "statement/while" {
779 initScanChild(item, "condition", source, table, item.lo, locals, violation)
780 }
781 ()
784function validateImportedInitializers(
785 modules: Array<ModuleDraft>,
786 tables: ModuleTableCatalog,
787 diagnostics: Array<PreviewResolvedDiagnostic>,
788) -> () {
789 let mut moduleIndexValue = 0
790 for module in modules {
791 if !module.result.entry {
792 let source = module.source
793 let table = tables.tables[moduleIndexValue]
794 for statement in module.root.children {
795 let item = unwrapExport(statement)
796 let mut violation: Array<InitViolation> = []
797 initScanItem(item, source, table, violation)
798 match violation.get(0) {
799 case Some(found) => {
800 let owners = initItemNames(item, source)
801 let owner = if owners.length == 0 {
802 spanText(source, found.lo, found.hi)
803 } else {
804 stringJoin(owners, ", ")
805 }
806 diagnostic(
807 diagnostics,
808 moduleIndexValue,
809 "TPZ3018",
810 "the initializer of `{owner}` reaches `{found.name}`, whose initializer has not completed (v5.2 defines no partially initialized module binding)",
811 found.lo,
812 found.hi,
813 )
814 }
815 case None => ()
816 }
817 }
818 }
819 moduleIndexValue += 1
820 }
821 ()
824export function resolvedResult(
825 modules: Array<ModuleDraft>,
826 publicEdges: Array<PreviewImportEdge>,
827 tables: ModuleTableCatalog,
828 diagnostics: Array<PreviewResolvedDiagnostic>,
829) -> Result<ResolveResult, string> {
830 let outputs = ResolverOutputs {
831 modules: [],
832 scopes: [],
833 declarations: [],
834 references: [],
835 exports: [],
836 }
837 let mut index = 0
838 for module in modules {
839 resolveModuleFacts(module, index, tables, outputs, diagnostics)
840 index += 1
841 }
842 if diagnostics.length == 0 {
843 validateImportedInitializers(modules, tables, diagnostics)
844 }
845 Ok(ResolveResult {
846 queries: [],
847 modules: outputs.modules,
848 edges: publicEdges,
849 scopes: outputs.scopes,
850 declarations: outputs.declarations,
851 references: outputs.references,
852 exports: outputs.exports,
853 diagnostics: diagnostics,
854 })
857export function rejectedResult(
858 modules: Array<ModuleDraft>,
859 publicEdges: Array<PreviewImportEdge>,
860 diagnostics: Array<PreviewResolvedDiagnostic>,
861) -> ResolveResult {
862 let mut moduleResults: Array<PreviewModuleResult> = []
863 for module in modules {
864 moduleResults.push(module.result)
865 }
866 ResolveResult {
867 queries: [],
868 modules: moduleResults,
869 edges: publicEdges,
870 scopes: [],
871 declarations: [],
872 references: [],
873 exports: [],
874 diagnostics: diagnostics,
875 }

SHA256 05bccf54250f6586d5aad861cc9c1b16271bf130a760b95f4b08bb25d2ec545f