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 or3
// rejected resolver outputs for the facade.5
import types {6
PreviewImportEdge,7
PreviewModuleResult,8
PreviewResolvedDeclaration,9
PreviewResolvedDiagnostic,10
PreviewResolvedExport,11
PreviewResolvedReference,12
PreviewResolvedScope,13
ResolveResult,14
SyntaxNode,15
}16
import unicode { didYouMean }17
import 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,33
}34
import resolver.modules_tables {35
catalogTable,36
effectiveNamespace,37
exportedBinding,38
exportedNames,39
tableBinding,40
unwrapExport,41
}42
import resolver.scopes_walk {43
addDeclaration,44
appendModuleExports,45
walkNode,46
}48
function privatePublicSurfaceTypeBinding(49
table: ModuleTable,50
name: string,51
) -> bool {52
let binding = match tableBinding(table, name) {53
case Some(found) => found54
case None => return false55
}56
binding.namespace == "type" &&57
!binding.exported &&58
(binding.declarationKind == "type-alias" ||59
binding.declarationKind == "nominal-type")60
}62
function 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
()90
}92
function 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
()160
}162
function validateExportSignatures(163
module: ModuleDraft,164
table: ModuleTable,165
diagnostics: Array<PreviewResolvedDiagnostic>,166
) -> () {167
let source = module.source168
for statement in module.root.children {169
validateExportSignature(source, statement, table, diagnostics)170
}171
()172
}174
export function validateImportSurfaces(175
modules: Array<ModuleDraft>,176
tables: ModuleTableCatalog,177
diagnostics: Array<PreviewResolvedDiagnostic>,178
) -> () {179
let mut importingIndex = 0180
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 = 0194
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 += 1210
}211
}212
case None => ()213
}214
}215
validateExportSignatures(module, tables.tables[importingIndex], diagnostics)216
importingIndex += 1217
}218
()219
}221
function resolveModuleFacts(222
module: ModuleDraft,223
index: int,224
tables: ModuleTableCatalog,225
outputs: ResolverOutputs,226
diagnostics: Array<PreviewResolvedDiagnostic>,227
) -> () {228
let mut moduleResults = outputs.modules229
moduleResults.push(module.result)230
let mut scopes = outputs.scopes231
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
()272
}274
function 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 = output283
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 = output322
names.push(text(source, name))323
}324
case None => ()325
}326
}327
}328
}329
()330
}332
function 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
output349
}351
function initLocalContains(locals: Array<string>, name: string) -> bool {352
for local in locals {353
if local == name {354
return true355
}356
}357
false358
}360
function 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 spans376
// are ordered and non-overlapping, declaration byte >= current item byte is377
// 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) => found380
case None => return ()381
}382
if (binding.declarationKind == "let" ||383
binding.declarationKind == "function") && binding.lo >= itemLo {384
let mut found = violation385
found.push(InitViolation { name: name, lo: node.lo, hi: node.hi })386
}387
()388
}390
function 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
()411
}413
function 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
()426
}428
function 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
()451
}453
function 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 = true473
match child(expression, "callee") {474
case Some(callee) => {475
if callee.kind == "expression/optional-access" {476
argumentsImmediate = false477
}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
()665
}667
function 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 = locals675
for name in bound {676
active.push(name)677
}678
()679
}681
function 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.length690
let mut active = locals691
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.length715
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
()755
}757
function 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
()782
}784
function validateImportedInitializers(785
modules: Array<ModuleDraft>,786
tables: ModuleTableCatalog,787
diagnostics: Array<PreviewResolvedDiagnostic>,788
) -> () {789
let mut moduleIndexValue = 0790
for module in modules {791
if !module.result.entry {792
let source = module.source793
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 += 1820
}821
()822
}824
export 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 = 0838
for module in modules {839
resolveModuleFacts(module, index, tables, outputs, diagnostics)840
index += 1841
}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
})855
}857
export 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
}876
}SHA256 05bccf54250f6586d5aad861cc9c1b16271bf130a760b95f4b08bb25d2ec545f