Appearance
| 1 | namespace Compiler is | |
| 2 | use System.Exception; | |
| 3 | ||
| 4 | use IO.Std; | |
| 5 | ||
| 6 | use Collections.Iterable; | |
| 7 | use Collections.MutableList; | |
| 8 | use Collections.LIST; | |
| 9 | ||
| 10 | use IoC; | |
| 11 | use Logging; | |
| 12 | ||
| 13 | class COMPILER is | |
| 14 | container: CONTAINER; | |
| 15 | logger: Logger; | |
| 16 | ||
| 17 | source_files: LIST[SOURCE_FILE]; | |
| 18 | ||
| 19 | post_parse_passes: MutableList[Pass]; | |
| 20 | ||
| 21 | build_passes: MutableList[Pass]; | |
| 22 | ||
| 23 | _compile_expressions_pass: Pass; | |
| 24 | ||
| 25 | is_full_compile_needed: bool public; | |
| 26 | ||
| 27 | // True when the declaration-level state - symbols, ancestries, | |
| 28 | // override links, store-free bits - reflects every registered | |
| 29 | // file's current source: set by a completed build, cleared the | |
| 30 | // moment clear_symbols abandons the tables. Distinct from | |
| 31 | // is_full_compile_needed, which tracks whether expression-level | |
| 32 | // DIAGNOSTICS are stale for some files; right after an | |
| 33 | // interface-affecting EDIT's rebuild the tables are current while | |
| 34 | // a full compile is still needed. | |
| 35 | are_tables_current: bool public; | |
| 36 | ||
| 37 | // The client's open-file set changed since the last full compile. | |
| 38 | // Editor-only hints are gated on open files at emission time, so | |
| 39 | // files that are already compiled through expressions need their | |
| 40 | // walks re-run from cleared state - the expressions-only COMPILE | |
| 41 | // cannot serve that; the next COMPILE takes the full rebuild. | |
| 42 | is_open_set_changed: bool public; | |
| 43 | ||
| 44 | generated_source_files: MutableList[string]; | |
| 45 | ||
| 46 | init() is | |
| 47 | container = IoC.CONTAINER.instance; | |
| 48 | logger = container.logger; | |
| 49 | ||
| 50 | source_files = LIST[SOURCE_FILE](); | |
| 51 | generated_source_files = LIST[string](); | |
| 52 | ||
| 53 | post_parse_passes = LIST[Pass](); | |
| 54 | add_pass(post_parse_passes, "conditional-compilation", (source_file) -> bool => conditional_compilation_pass(source_file)); | |
| 55 | add_pass(post_parse_passes, "collect-modifier-keyword-locations", (source_file) -> bool => collect_modifier_keyword_locations_pass(source_file)); | |
| 56 | add_pass(post_parse_passes, "rewrite-syntax-trees", (source_file) -> bool => rewrite_syntax_tree_pass(source_file)); | |
| 57 | add_pass(post_parse_passes, "collect-suppress-pragmas", (source_file) -> bool => collect_suppress_pragmas_pass(source_file)); | |
| 58 | ||
| 59 | build_passes = LIST[Pass](); | |
| 60 | // Declares the type-level skeleton only: namespaces, types, | |
| 61 | // variants and type parameters - everything a type expression | |
| 62 | // can name. Members are declared by declare-members below. | |
| 63 | add_pass(build_passes, "declare-symbols", (source_file) -> bool => declare_symbols_pass(source_file)); | |
| 64 | // First round: binds imports of namespaces and types. Imports | |
| 65 | // of members bind in resolve-member-uses, once they exist. | |
| 66 | add_pass(build_passes, "resolve-uses", (source_file) -> bool => resolve_uses_pass(source_file)); | |
| 67 | // Runs after resolve-uses so a member - in particular an | |
| 68 | // impl/partial block's target name - can be reached through a | |
| 69 | // use import and declared anywhere relative to its uses. | |
| 70 | add_pass(build_passes, "declare-members", (source_file) -> bool => declare_members_pass(source_file)); | |
| 71 | // Second round: binds imports of members - static methods, | |
| 72 | // global functions, enum members - and reports anything still | |
| 73 | // unresolved. | |
| 74 | add_pass(build_passes, "resolve-member-uses", (source_file) -> bool => resolve_member_uses_pass(source_file)); | |
| 75 | // Runs after declare-members so it can read each class's | |
| 76 | // computed `is_abstract` (explicit + implicit) rather than | |
| 77 | // just the syntactic modifier. | |
| 78 | // Walks every file to report warnings but leaves nothing a | |
| 79 | // later pass reads, so it reports its walk without advancing | |
| 80 | // the compiled_through marker. | |
| 81 | add_pass(build_passes, "check-name-conventions", (source_file) -> bool => check_name_conventions_pass(source_file)) | |
| 82 | .advances_milestone = false; | |
| 83 | add_pass(build_passes, "resolve-type-expressions", (source_file: SOURCE_FILE) -> bool => resolve_type_expressions_pass(source_file)); | |
| 84 | add_pass(build_passes, "resolve-ancestors", (source_file) -> bool => resolve_ancestors_pass(source_file)); | |
| 85 | add_pass(build_passes, "resolve-explicit-types", (source_file) -> bool => resolve_explicit_types_pass(source_file)); | |
| 86 | add_pass(build_passes, "check-type-argument-bounds", (source_file) -> bool => check_type_argument_bounds_pass(source_file)); | |
| 87 | add_pass( | |
| 88 | build_passes, | |
| 89 | "resolve-overrides", | |
| 90 | () -> void is si, | |
| 91 | (source_file) -> bool => resolve_overrides_pass(source_file), | |
| 92 | () -> void is | |
| 93 | container.resolve_overrides.check_duplicate_global_functions(); | |
| 94 | si | |
| 95 | ); | |
| 96 | add_pass( | |
| 97 | build_passes, | |
| 98 | "infer-store-free", | |
| 99 | () -> void is | |
| 100 | container.infer_store_free.start_run(); | |
| 101 | si, | |
| 102 | (source_file) -> bool => infer_store_free_pass(source_file), | |
| 103 | () -> void is | |
| 104 | container.infer_store_free.finish_run(); | |
| 105 | si | |
| 106 | ); | |
| 107 | add_pass(build_passes, "register-source-intrinsics", (source_file) -> bool => register_source_intrinsics_pass(source_file)); | |
| 108 | add_pass(build_passes, "check-pure-overrides", (source_file) -> bool => check_pure_overrides_pass(source_file)); | |
| 109 | add_pass(build_passes, "record-type-argument-uses", (source_file) -> bool => record_type_argument_uses_pass(source_file)); | |
| 110 | ||
| 111 | add_pass(build_passes, "mark-boxed-locals", (source_file) -> bool => mark_boxed_locals_pass(source_file)); | |
| 112 | ||
| 113 | _compile_expressions_pass = add_pass(build_passes, "compile-expressions", (source_file) -> bool => compile_expressions_pass(source_file)); | |
| 114 | ||
| 115 | add_pass( | |
| 116 | build_passes, | |
| 117 | "generate-il", | |
| 118 | () -> void is | |
| 119 | container.referenced_assemblies.gen(); | |
| 120 | si, | |
| 121 | (source_file) -> bool => generate_il_pass(source_file), | |
| 122 | () -> void is | |
| 123 | // An executable build with no `.entrypoint` emitted would | |
| 124 | // otherwise reach ilasm, which fails with the cryptic "No | |
| 125 | // entry point declared for executable". Surface it as a | |
| 126 | // located compiler error instead; `finish_build` aborts | |
| 127 | // on `any_errors` before ilasm runs. Library builds | |
| 128 | // (`--library`) legitimately have no entry point. Gated | |
| 129 | // on no prior errors so it doesn't pile onto a build | |
| 130 | // already failing for another reason (an error-aborted | |
| 131 | // build never reaches `.entrypoint` emission, so | |
| 132 | // `seen_entrypoint` is false regardless of whether the | |
| 133 | // source declares an `entry`). | |
| 134 | let flags = container.build_flags; | |
| 135 | ||
| 136 | if flags.want_executable | |
| 137 | /\ !flags.want_library | |
| 138 | /\ !container.ir_context.seen_entrypoint | |
| 139 | /\ !container.logger.any_errors | |
| 140 | then | |
| 141 | container.logger.error( | |
| 142 | Source.LOCATION.internal, | |
| 143 | "no entry point declared; add an 'entry()' function or build with --library" | |
| 144 | ); | |
| 145 | fi | |
| 146 | si | |
| 147 | ); | |
| 148 | ||
| 149 | // Pass.order is set in registration order so a SOURCE_FILE's | |
| 150 | // compiled_through.order can be compared numerically against the | |
| 151 | // order of a target pass (e.g. _compile_expressions_pass). Post- | |
| 152 | // parse passes get their own counter starting at zero and don't | |
| 153 | // participate in compiled_through queries. | |
| 154 | assign_pass_orders(post_parse_passes); | |
| 155 | assign_pass_orders(build_passes); | |
| 156 | si | |
| 157 | ||
| 158 | assign_pass_orders(passes: Collections.Iterable[Pass]) static is | |
| 159 | let order mut = 0; | |
| 160 | for p in passes do | |
| 161 | p.order = order; | |
| 162 | order = order + 1; | |
| 163 | od | |
| 164 | si | |
| 165 | ||
| 166 | dump_counts() is | |
| 167 | container.namespaces.dump_counts(); | |
| 168 | container.symbol_use_locations.dump_counts(); | |
| 169 | container.symbol_definition_locations.dump_counts(); | |
| 170 | si | |
| 171 | ||
| 172 | clear_symbols() is | |
| 173 | are_tables_current = false; | |
| 174 | ||
| 175 | container.symbol_table.clear(); | |
| 176 | container.namespaces.clear(); | |
| 177 | container.symbol_use_locations.clear(); | |
| 178 | container.symbol_definition_locations.clear(); | |
| 179 | Semantic.Symbols.STORE_FREE_BITS.clear(); | |
| 180 | Semantic.Symbols.MEMBER_OPERATOR_NAMES.clear(); | |
| 181 | // container.logger.clear(); | |
| 182 | ||
| 183 | // Every previously-queued file's expression-level state now | |
| 184 | // references symbols that have just been abandoned. Drop the | |
| 185 | // marker so query handlers can tell, and the retained | |
| 186 | // store-free facts, whose keys and callee edges are those | |
| 187 | // abandoned symbols. | |
| 188 | for f in source_files do | |
| 189 | f.compiled_through = null; | |
| 190 | f.store_free_facts = null; | |
| 191 | od | |
| 192 | si | |
| 193 | ||
| 194 | is_compiled_through_expressions(source_file: SOURCE_FILE) -> bool => | |
| 195 | let compiled_through = source_file?.compiled_through in | |
| 196 | compiled_through? /\ | |
| 197 | compiled_through.order >= _compile_expressions_pass.order; | |
| 198 | ||
| 199 | clear_queue() is | |
| 200 | source_files.clear(); | |
| 201 | si | |
| 202 | ||
| 203 | add_pass(passes: Collections.MutableList[Pass], description: string, apply: SOURCE_FILE -> bool) -> Pass is | |
| 204 | let pass = PASS(container.timers, description, null, apply, null); | |
| 205 | passes.add(pass); | |
| 206 | return pass; | |
| 207 | si | |
| 208 | ||
| 209 | add_pass( | |
| 210 | passes: Collections.MutableList[Pass], | |
| 211 | description: string, | |
| 212 | start: () -> void, | |
| 213 | apply: SOURCE_FILE -> bool, | |
| 214 | finish: () -> void | |
| 215 | ) -> Pass | |
| 216 | is | |
| 217 | let pass = PASS(container.timers, description, start, apply, finish); | |
| 218 | passes.add(pass); | |
| 219 | return pass; | |
| 220 | si | |
| 221 | ||
| 222 | add_pass(passes: Collections.MutableList[Pass], pass: Pass) is | |
| 223 | passes.add(pass); | |
| 224 | si | |
| 225 | ||
| 226 | parse_and_queue(path: string, reader: IO.TextReader, want_compile_up_to_expressions: bool, want_compile_expressions: bool, is_internal_file: bool) is | |
| 227 | queue( | |
| 228 | parse(path, reader, want_compile_up_to_expressions, want_compile_expressions, is_internal_file) | |
| 229 | ); | |
| 230 | si | |
| 231 | ||
| 232 | queue(source_file: SOURCE_FILE) is | |
| 233 | source_files.add(source_file); | |
| 234 | si | |
| 235 | ||
| 236 | queue(source_files: Collections.Iterable[SOURCE_FILE]) is | |
| 237 | self.source_files.add_range(source_files); | |
| 238 | si | |
| 239 | ||
| 240 | parse(path: string, reader: IO.TextReader, want_compile_up_to_expressions: bool, want_compile_expressions: bool, is_internal_file: bool) -> SOURCE_FILE => | |
| 241 | parse(path, reader, want_compile_up_to_expressions, want_compile_expressions, is_internal_file, logger); | |
| 242 | ||
| 243 | // Parsing into an explicitly-supplied logger lets a caller (the | |
| 244 | // analysis-mode format handlers) parse without touching the shared | |
| 245 | // diagnostics store: a throwaway logger keeps parse diagnostics and | |
| 246 | // the speculation state stack isolated from the live analyser state. | |
| 247 | parse( | |
| 248 | path: string, | |
| 249 | reader: IO.TextReader, | |
| 250 | want_compile_up_to_expressions: bool, | |
| 251 | want_compile_expressions: bool, | |
| 252 | is_internal_file: bool, | |
| 253 | logger: Logger | |
| 254 | ) -> SOURCE_FILE is | |
| 255 | let tokenizer = Lexical.TOKENIZER( | |
| 256 | logger, | |
| 257 | path, | |
| 258 | reader, | |
| 259 | is_internal_file | |
| 260 | ); | |
| 261 | ||
| 262 | // Speculative-lookahead ring buffer; a power of 2 (the | |
| 263 | // queue masks indices with `size - 1`), sized generously so | |
| 264 | // deep-but-finite speculation does not overflow it. A true | |
| 265 | // speculation loop is caught separately by the tokenizer's | |
| 266 | // backtrack-loop detector. | |
| 267 | let token_queue = Lexical.TOKEN_QUEUE(2048); | |
| 268 | ||
| 269 | let token_lookahead = Lexical.TOKEN_LOOKAHEAD( | |
| 270 | token_queue, | |
| 271 | tokenizer | |
| 272 | ); | |
| 273 | ||
| 274 | let context = Syntax.Parsers.CONTEXT( | |
| 275 | token_lookahead, | |
| 276 | logger | |
| 277 | ); | |
| 278 | ||
| 279 | // The file root parses as a definition list at namespace depth 0, | |
| 280 | // where a file with no namespace may carry bare top-level | |
| 281 | // statements (collected into a synthesised entry point). | |
| 282 | let definitions = container.definition_global_list_parser.parse(context)!; | |
| 283 | ||
| 284 | let result = | |
| 285 | SOURCE_FILE( | |
| 286 | want_compile_up_to_expressions, | |
| 287 | want_compile_expressions, | |
| 288 | path, | |
| 289 | definitions | |
| 290 | ); | |
| 291 | ||
| 292 | result.trivia = tokenizer.trivia; | |
| 293 | ||
| 294 | return result; | |
| 295 | si | |
| 296 | ||
| 297 | post_parse(source_files: Iterable[SOURCE_FILE]) is | |
| 298 | build(post_parse_passes, source_files); | |
| 299 | si | |
| 300 | ||
| 301 | post_parse() is | |
| 302 | build(post_parse_passes, source_files); | |
| 303 | si | |
| 304 | ||
| 305 | build() is | |
| 306 | container.ghul_namespace_creator.create_namespaces(); | |
| 307 | build(build_passes, source_files); | |
| 308 | ||
| 309 | are_tables_current = true; | |
| 310 | si | |
| 311 | ||
| 312 | build(passes: Collections.Iterable[Pass], source_files: Collections.Iterable[SOURCE_FILE]) is | |
| 313 | let is_compiling_expressions mut = false; | |
| 314 | ||
| 315 | for pass in passes do | |
| 316 | if pass == _compile_expressions_pass then | |
| 317 | is_compiling_expressions = true; | |
| 318 | fi | |
| 319 | ||
| 320 | logger.set_is_compiling_expressions(is_compiling_expressions); | |
| 321 | ||
| 322 | pass.start(); | |
| 323 | ||
| 324 | for i in source_files do | |
| 325 | let symbol_table = IoC.CONTAINER.instance.symbol_table; | |
| 326 | let symbol_table_mark = symbol_table.mark_scope_stack(); | |
| 327 | let diagnostics_mark = logger.mark(); | |
| 328 | let symbol_uses_mark = container.symbol_use_locations.mark(); | |
| 329 | ||
| 330 | let did_work mut = false; | |
| 331 | ||
| 332 | try | |
| 333 | did_work = pass.apply(i); | |
| 334 | catch e: Exception | |
| 335 | logger.exception(i.definition.location, e, "caught exception running pass {pass} on work item {i}"); | |
| 336 | finally | |
| 337 | logger.release(diagnostics_mark); | |
| 338 | container.symbol_use_locations.release(symbol_uses_mark); | |
| 339 | symbol_table.release_scope_stack(symbol_table_mark); | |
| 340 | yrt | |
| 341 | ||
| 342 | if did_work then | |
| 343 | WORK_COUNTERS.file_pass(container.timers, pass.description); | |
| 344 | ||
| 345 | if pass.advances_milestone then | |
| 346 | i.compiled_through = pass; | |
| 347 | fi | |
| 348 | fi | |
| 349 | ||
| 350 | IoC.CONTAINER.instance.namespaces.pop_all_namespaces(); | |
| 351 | od | |
| 352 | ||
| 353 | pass.finish(); | |
| 354 | od | |
| 355 | si | |
| 356 | ||
| 357 | // On-demand expression compile for one file against the retained | |
| 358 | // symbol table, replaying only the pipeline's compile-expressions | |
| 359 | // step with the same per-file bookkeeping as the build loop. | |
| 360 | // Valid when the file has been built up to expressions in the | |
| 361 | // current symbol generation - declare/resolve state current, only | |
| 362 | // the expression-level walk missing. The analysis-mode query-miss | |
| 363 | // recompile takes this instead of a whole-project rebuild when no | |
| 364 | // interface change is pending. | |
| 365 | compile_expressions_only(source_file: SOURCE_FILE) is | |
| 366 | source_file.want_compile_expressions = true; | |
| 367 | ||
| 368 | logger.set_is_compiling_expressions(true); | |
| 369 | ||
| 370 | let symbol_table = container.symbol_table; | |
| 371 | let symbol_table_mark = symbol_table.mark_scope_stack(); | |
| 372 | let diagnostics_mark = logger.mark(); | |
| 373 | let symbol_uses_mark = container.symbol_use_locations.mark(); | |
| 374 | ||
| 375 | let did_work mut = false; | |
| 376 | ||
| 377 | try | |
| 378 | did_work = _compile_expressions_pass.apply(source_file); | |
| 379 | catch e: Exception | |
| 380 | logger.exception(source_file.definition.location, e, "caught exception compiling expressions on demand for {source_file}"); | |
| 381 | finally | |
| 382 | logger.release(diagnostics_mark); | |
| 383 | container.symbol_use_locations.release(symbol_uses_mark); | |
| 384 | symbol_table.release_scope_stack(symbol_table_mark); | |
| 385 | ||
| 386 | container.namespaces.pop_all_namespaces(); | |
| 387 | ||
| 388 | logger.set_is_compiling_expressions(false); | |
| 389 | yrt | |
| 390 | ||
| 391 | if did_work then | |
| 392 | WORK_COUNTERS.file_pass(container.timers, _compile_expressions_pass.description); | |
| 393 | ||
| 394 | source_file.compiled_through = _compile_expressions_pass; | |
| 395 | fi | |
| 396 | si | |
| 397 | ||
| 398 | // Re-derive every function's store-free bit from the retained | |
| 399 | // ASTs and report whether any bit moved. The incremental EDIT | |
| 400 | // paths recompile expressions without re-running infer-store-free, | |
| 401 | // so a body edit that adds or drops a store, or changes a callee | |
| 402 | // set, can flip a callee's bit while every caller's narrowing was | |
| 403 | // computed against the old one. The whole-program fixpoint needs | |
| 404 | // facts from every file, so this walks them all; comparing the | |
| 405 | // fresh bits against the previous run tells the debounced compile | |
| 406 | // whether trusting the incremental expression state is still | |
| 407 | // sound. compiled_through is untouched - only the bits are. The | |
| 408 | // file set is passed in: the compiler's own queue is cleared | |
| 409 | // between builds, so the analysis handler owns the live set. | |
| 410 | refresh_store_free(files: Collections.Iterable[SOURCE_FILE]) -> bool is | |
| 411 | let symbol_table = container.symbol_table; | |
| 412 | ||
| 413 | container.infer_store_free.start_run(); | |
| 414 | ||
| 415 | for source_file in files do | |
| 416 | source_file.want_compile_up_to_expressions = true; | |
| 417 | ||
| 418 | let symbol_table_mark = symbol_table.mark_scope_stack(); | |
| 419 | ||
| 420 | try | |
| 421 | infer_store_free_pass(source_file); | |
| 422 | catch e: Exception | |
| 423 | logger.exception(source_file.definition.location, e, "caught exception refreshing store-free bits for {source_file}"); | |
| 424 | finally | |
| 425 | symbol_table.release_scope_stack(symbol_table_mark); | |
| 426 | container.namespaces.pop_all_namespaces(); | |
| 427 | yrt | |
| 428 | od | |
| 429 | ||
| 430 | container.infer_store_free.finish_run(); | |
| 431 | ||
| 432 | return container.infer_store_free.last_run_changed; | |
| 433 | si | |
| 434 | ||
| 435 | // The functions the most recent refresh_store_free flipped, or | |
| 436 | // null when the flip set could not be bounded. Read by the | |
| 437 | // debounced compile to true up just the files referencing a | |
| 438 | // flipped function instead of rebuilding the whole project. | |
| 439 | last_store_free_flips: Collections.List[Semantic.Symbols.Function]? => | |
| 440 | container.infer_store_free.last_flipped; | |
| 441 | ||
| 442 | // How many files the most recent refresh_store_free actually | |
| 443 | // re-walked, versus serving retained facts. Refresh telemetry. | |
| 444 | last_store_free_walked_files: int => | |
| 445 | container.infer_store_free.last_run_walked_files; | |
| 446 | ||
| 447 | // Recompile one file's expressions against the current symbol | |
| 448 | // table, keeping its resolved interface. A CE-output-only clear | |
| 449 | // (preserve_resolved_types) drops the file's compile-expressions | |
| 450 | // output - including the node-keyed stores - while leaving | |
| 451 | // TypeExpression.type intact, so re-running compile-expressions | |
| 452 | // rebinds every body without re-resolving (and thus without | |
| 453 | // double-setting) the retained interface types. | |
| 454 | recompile_file_expressions(source_file: SOURCE_FILE) is | |
| 455 | let ce_clear = Syntax.Process.CLEAR_STATE_VISITOR(true); | |
| 456 | ce_clear.apply(source_file.definition); | |
| 457 | ||
| 458 | container.state_store_registry.drop_file(source_file.file_name); | |
| 459 | logger.clear_expression_diagnostics(source_file.file_name); | |
| 460 | ||
| 461 | // this file's bodies are rebinding, so facts derived from | |
| 462 | // the old bindings no longer describe it | |
| 463 | source_file.store_free_facts = null; | |
| 464 | ||
| 465 | compile_expressions_only(source_file); | |
| 466 | si | |
| 467 | ||
| 468 | // Declare, resolve and compile just the definitions an append-only | |
| 469 | // interface edit added to the end of a file, against the retained | |
| 470 | // symbol table. The appended subtree is new - nothing referenced | |
| 471 | // it before this edit - so no retained symbol changes and no other | |
| 472 | // file's state is touched: each pass walks only the new nodes. | |
| 473 | // infer-store-free is not re-run; the new functions carry no | |
| 474 | // store-free bit, which is the sound not-proven default until the | |
| 475 | // next full rebuild. Namespaces named by the new declarations | |
| 476 | // merge into the retained namespace map exactly as they would in | |
| 477 | // a full build. | |
| 478 | build_appended(source_file: SOURCE_FILE, new_definitions: Syntax.Trees.Definitions.LIST) is | |
| 479 | source_file.want_compile_up_to_expressions = true; | |
| 480 | source_file.want_compile_expressions = true; | |
| 481 | ||
| 482 | _run_appended_pass(source_file, "declare-symbols", () is container.declare_symbols.apply(new_definitions); si); | |
| 483 | _run_appended_pass(source_file, "resolve-uses", () is container.resolve_uses.apply(new_definitions); si); | |
| 484 | _run_appended_pass(source_file, "declare-members", () is container.declare_members.apply(new_definitions); si); | |
| 485 | _run_appended_pass(source_file, "resolve-member-uses", () is container.resolve_member_uses.apply(new_definitions); si); | |
| 486 | _run_appended_pass(source_file, "resolve-type-expressions", () is container.resolve_type_expressions.apply(new_definitions); si); | |
| 487 | _run_appended_pass(source_file, "resolve-ancestors", () is container.resolve_ancestors.apply(new_definitions); si); | |
| 488 | _run_appended_pass(source_file, "resolve-explicit-types", () is container.resolve_explicit_types.apply(new_definitions); si); | |
| 489 | _run_appended_pass(source_file, "check-type-argument-bounds", () is container.check_type_argument_bounds.apply(new_definitions); si); | |
| 490 | _run_appended_pass(source_file, "resolve-overrides", () is container.resolve_overrides.apply(new_definitions); si); | |
| 491 | ||
| 492 | logger.set_is_compiling_expressions(true); | |
| 493 | ||
| 494 | _run_appended_pass(source_file, "compile-expressions", () is container.compile_expressions.apply(new_definitions); si); | |
| 495 | ||
| 496 | logger.set_is_compiling_expressions(false); | |
| 497 | si | |
| 498 | ||
| 499 | // Declare and resolve a container's changed function members | |
| 500 | // against the retained symbol table, with the scope cursor | |
| 501 | // positioned inside the enclosing namespace and class scopes the | |
| 502 | // way the build loop's tree walk would have it. Split from the | |
| 503 | // expression walk so a class's pull-down can re-run between the | |
| 504 | // two: overrides and pulled-down members must reflect the new | |
| 505 | // member set before any body compiles against them. | |
| 506 | build_members_interface( | |
| 507 | source_file: SOURCE_FILE, | |
| 508 | enclosing: Collections.List[Syntax.Trees.Definitions.Definition], | |
| 509 | members: Syntax.Trees.Definitions.LIST | |
| 510 | ) is | |
| 511 | source_file.want_compile_up_to_expressions = true; | |
| 512 | source_file.want_compile_expressions = true; | |
| 513 | ||
| 514 | _run_positioned_pass(source_file, enclosing, "declare-members", () is container.declare_members.apply(members); si); | |
| 515 | _run_positioned_pass(source_file, enclosing, "resolve-type-expressions", () is container.resolve_type_expressions.apply(members); si); | |
| 516 | _run_positioned_pass(source_file, enclosing, "resolve-explicit-types", () is container.resolve_explicit_types.apply(members); si); | |
| 517 | _run_positioned_pass(source_file, enclosing, "check-type-argument-bounds", () is container.check_type_argument_bounds.apply(members); si); | |
| 518 | si | |
| 519 | ||
| 520 | build_members_expressions( | |
| 521 | source_file: SOURCE_FILE, | |
| 522 | enclosing: Collections.List[Syntax.Trees.Definitions.Definition], | |
| 523 | members: Syntax.Trees.Definitions.LIST | |
| 524 | ) is | |
| 525 | logger.set_is_compiling_expressions(true); | |
| 526 | ||
| 527 | _run_positioned_pass(source_file, enclosing, "compile-expressions", () is container.compile_expressions.apply(members); si); | |
| 528 | ||
| 529 | logger.set_is_compiling_expressions(false); | |
| 530 | si | |
| 531 | ||
| 532 | // One pass application over a changed member subtree, with the | |
| 533 | // symbol-table and namespace cursors positioned inside the | |
| 534 | // enclosing scopes first - a namespace enters through its own | |
| 535 | // node's scope plus the namespace prefix stack, a class-like | |
| 536 | // definition through its own node's scope, mirroring | |
| 537 | // ScopedVisitor. The finally's release and pop restore both | |
| 538 | // cursors however deep the positioning got. | |
| 539 | _run_positioned_pass( | |
| 540 | source_file: SOURCE_FILE, | |
| 541 | enclosing: Collections.List[Syntax.Trees.Definitions.Definition], | |
| 542 | pass_name: string, | |
| 543 | apply_pass: () -> void | |
| 544 | ) is | |
| 545 | WORK_COUNTERS.subtree_pass(container.timers, pass_name); | |
| 546 | ||
| 547 | let symbol_table = container.symbol_table; | |
| 548 | let symbol_table_mark = symbol_table.mark_scope_stack(); | |
| 549 | let diagnostics_mark = logger.mark(); | |
| 550 | let symbol_uses_mark = container.symbol_use_locations.mark(); | |
| 551 | ||
| 552 | try | |
| 553 | for node in enclosing do | |
| 554 | if let namespace_definition: Syntax.Trees.Definitions.NAMESPACE = node then | |
| 555 | container.namespaces.enter_namespace(namespace_definition.name.location, namespace_definition.name.name); | |
| 556 | symbol_table.enter_scope(namespace_definition); | |
| 557 | elif let carrier: Syntax.Trees.ScopeCarrier = node then | |
| 558 | symbol_table.enter_scope(carrier); | |
| 559 | else | |
| 560 | logger.poison(node.location, "no scope found for {node.get_type()}"); | |
| 561 | fi | |
| 562 | od | |
| 563 | ||
| 564 | apply_pass(); | |
| 565 | catch e: Exception | |
| 566 | logger.exception(source_file.definition.location, e, "caught exception building changed members in {source_file}"); | |
| 567 | finally | |
| 568 | logger.release(diagnostics_mark); | |
| 569 | container.symbol_use_locations.release(symbol_uses_mark); | |
| 570 | symbol_table.release_scope_stack(symbol_table_mark); | |
| 571 | ||
| 572 | container.namespaces.pop_all_namespaces(); | |
| 573 | yrt | |
| 574 | si | |
| 575 | ||
| 576 | // One pass application over an appended subtree, under the build | |
| 577 | // loop's per-file bookkeeping. | |
| 578 | _run_appended_pass(source_file: SOURCE_FILE, pass_name: string, apply_pass: () -> void) is | |
| 579 | WORK_COUNTERS.subtree_pass(container.timers, pass_name); | |
| 580 | ||
| 581 | let symbol_table = container.symbol_table; | |
| 582 | let symbol_table_mark = symbol_table.mark_scope_stack(); | |
| 583 | let diagnostics_mark = logger.mark(); | |
| 584 | let symbol_uses_mark = container.symbol_use_locations.mark(); | |
| 585 | ||
| 586 | try | |
| 587 | apply_pass(); | |
| 588 | catch e: Exception | |
| 589 | logger.exception(source_file.definition.location, e, "caught exception building appended declarations in {source_file}"); | |
| 590 | finally | |
| 591 | logger.release(diagnostics_mark); | |
| 592 | container.symbol_use_locations.release(symbol_uses_mark); | |
| 593 | symbol_table.release_scope_stack(symbol_table_mark); | |
| 594 | ||
| 595 | container.namespaces.pop_all_namespaces(); | |
| 596 | yrt | |
| 597 | si | |
| 598 | ||
| 599 | // Incremental body re-walk: re-process only the edited file's new | |
| 600 | // function bodies, after its donor bodies have been spliced in by the | |
| 601 | // analysis-mode EDIT handler. Every pass runs body-scoped — through | |
| 602 | // BODY_DECLARER for declare-symbols, BODY_REWALKER for the rest — so | |
| 603 | // the retained interface (arguments, signatures, indexer / property | |
| 604 | // parameters) is never re-processed. Re-resolving a retained, | |
| 605 | // already-typed argument would poison it ("set type twice"); the | |
| 606 | // bodies are leaves, so processing them alone is also sufficient. | |
| 607 | // resolve-uses / resolve-ancestors / resolve-overrides are skipped — | |
| 608 | // they do not visit body interiors and the interface is unchanged. | |
| 609 | // | |
| 610 | // Before the re-walk re-records the bodies, the edited file's stale | |
| 611 | // symbol-use / definition entries are reconciled: `correspondence` | |
| 612 | // gives every retained interface node's post-edit location, and | |
| 613 | // `body_spans` the pre-edit spans of the re-walked bodies. Body | |
| 614 | // entries are dropped (re-recorded here), interface entries and | |
| 615 | // symbols moved to their post-edit locations. | |
| 616 | rewalk_bodies( | |
| 617 | source_file: SOURCE_FILE, | |
| 618 | correspondence: Source.LOCATION_CORRESPONDENCE, | |
| 619 | body_spans: Source.BODY_SPANS | |
| 620 | ) is | |
| 621 | source_file.want_compile_up_to_expressions = true; | |
| 622 | source_file.want_compile_expressions = true; | |
| 623 | ||
| 624 | // Definition reconciliation first — it classifies symbols by | |
| 625 | // their pre-edit location, before the use reconciliation moves | |
| 626 | // the interface symbols. | |
| 627 | container.symbol_definition_locations.refresh_edited_file(source_file.file_name, body_spans); | |
| 628 | container.symbol_use_locations.refresh_edited_file(source_file.file_name, correspondence, body_spans); | |
| 629 | ||
| 630 | let flags = container.build_flags; | |
| 631 | let symbol_table = container.symbol_table; | |
| 632 | ||
| 633 | let body_declarer = Syntax.Process.BODY_DECLARER( | |
| 634 | logger, | |
| 635 | symbol_table, | |
| 636 | container.namespaces, | |
| 637 | container.declare_members | |
| 638 | ); | |
| 639 | ||
| 640 | // body-scoped declare-symbols | |
| 641 | WORK_COUNTERS.file_rewalk(container.timers, "declare-symbols"); | |
| 642 | ||
| 643 | let mark = symbol_table.mark_scope_stack(); | |
| 644 | ||
| 645 | try | |
| 646 | source_file.definition.walk(body_declarer); | |
| 647 | catch e: Exception | |
| 648 | logger.exception(source_file.definition.location, e, "caught exception declaring bodies in incremental re-walk of {source_file}"); | |
| 649 | finally | |
| 650 | symbol_table.release_scope_stack(mark); | |
| 651 | container.namespaces.pop_all_namespaces(); | |
| 652 | yrt | |
| 653 | ||
| 654 | rewalk_pass(source_file, "resolve-type-expressions", container.resolve_type_expressions); | |
| 655 | rewalk_pass(source_file, "resolve-explicit-types", container.resolve_explicit_types); | |
| 656 | rewalk_pass(source_file, "check-type-argument-bounds", container.check_type_argument_bounds); | |
| 657 | ||
| 658 | if flags.want_assembler \/ flags.want_executable then | |
| 659 | rewalk_pass(source_file, "record-type-argument-uses", container.record_type_argument_uses); | |
| 660 | fi | |
| 661 | ||
| 662 | logger.set_is_compiling_expressions(true); | |
| 663 | ||
| 664 | rewalk_pass(source_file, "compile-expressions", container.compile_expressions); | |
| 665 | ||
| 666 | logger.set_is_compiling_expressions(false); | |
| 667 | si | |
| 668 | ||
| 669 | // Run one body-visiting pass over the edited file's bodies only, | |
| 670 | // driven by BODY_REWALKER, under a scope-stack mark/release. The | |
| 671 | // pass name is passed separately because a body-visiting pass is | |
| 672 | // a bare visitor, not a registered Pass with a description. | |
| 673 | rewalk_pass(source_file: SOURCE_FILE, pass_name: string, pass: Syntax.Visitor) is | |
| 674 | WORK_COUNTERS.file_rewalk(container.timers, pass_name); | |
| 675 | ||
| 676 | let symbol_table = container.symbol_table; | |
| 677 | let mark = symbol_table.mark_scope_stack(); | |
| 678 | ||
| 679 | let rewalker = Syntax.Process.BODY_REWALKER( | |
| 680 | logger, | |
| 681 | symbol_table, | |
| 682 | container.namespaces, | |
| 683 | pass | |
| 684 | ); | |
| 685 | ||
| 686 | try | |
| 687 | source_file.definition.walk(rewalker); | |
| 688 | catch e: Exception | |
| 689 | logger.exception(source_file.definition.location, e, "caught exception in incremental re-walk of {source_file}"); | |
| 690 | finally | |
| 691 | symbol_table.release_scope_stack(mark); | |
| 692 | container.namespaces.pop_all_namespaces(); | |
| 693 | yrt | |
| 694 | si | |
| 695 | ||
| 696 | conditional_compilation_pass(source_file: SOURCE_FILE) -> bool is | |
| 697 | let definition = source_file.definition; | |
| 698 | ||
| 699 | container | |
| 700 | .conditional_compilation | |
| 701 | .apply( | |
| 702 | definition | |
| 703 | ); | |
| 704 | ||
| 705 | return true; | |
| 706 | si | |
| 707 | ||
| 708 | rewrite_syntax_tree_pass(source_file: SOURCE_FILE) -> bool is | |
| 709 | let definition = source_file.definition; | |
| 710 | ||
| 711 | container | |
| 712 | .rewrite_primary_constructors | |
| 713 | .apply(definition); | |
| 714 | ||
| 715 | container | |
| 716 | .synthesise_top_level_entry | |
| 717 | .apply(definition); | |
| 718 | ||
| 719 | container | |
| 720 | .expand_namespaces | |
| 721 | .apply(definition); | |
| 722 | ||
| 723 | container | |
| 724 | .add_accessors_for_properties | |
| 725 | .apply(definition); | |
| 726 | ||
| 727 | container | |
| 728 | .spill_awaits | |
| 729 | .apply(definition); | |
| 730 | ||
| 731 | return true; | |
| 732 | si | |
| 733 | ||
| 734 | collect_suppress_pragmas_pass(source_file: SOURCE_FILE) -> bool is | |
| 735 | container | |
| 736 | .collect_suppress_pragmas | |
| 737 | .apply(source_file); | |
| 738 | ||
| 739 | return true; | |
| 740 | si | |
| 741 | ||
| 742 | collect_modifier_keyword_locations_pass(source_file: SOURCE_FILE) -> bool is | |
| 743 | container | |
| 744 | .collect_modifier_keyword_locations | |
| 745 | .apply(source_file); | |
| 746 | ||
| 747 | return true; | |
| 748 | si | |
| 749 | ||
| 750 | check_name_conventions_pass(source_file: SOURCE_FILE) -> bool is | |
| 751 | container | |
| 752 | .check_name_conventions | |
| 753 | .apply(source_file); | |
| 754 | ||
| 755 | return true; | |
| 756 | si | |
| 757 | ||
| 758 | declare_symbols_pass(source_file: SOURCE_FILE) -> bool is | |
| 759 | let flags = container.build_flags; | |
| 760 | let definition = source_file.definition; | |
| 761 | ||
| 762 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 763 | container | |
| 764 | .declare_symbols | |
| 765 | .apply(definition); | |
| 766 | ||
| 767 | return true; | |
| 768 | fi | |
| 769 | ||
| 770 | return false; | |
| 771 | si | |
| 772 | ||
| 773 | resolve_uses_pass(source_file: SOURCE_FILE) -> bool is | |
| 774 | let flags = container.build_flags; | |
| 775 | let definition = source_file.definition; | |
| 776 | ||
| 777 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 778 | container | |
| 779 | .resolve_uses | |
| 780 | .apply(definition); | |
| 781 | ||
| 782 | return true; | |
| 783 | fi | |
| 784 | ||
| 785 | return false; | |
| 786 | si | |
| 787 | ||
| 788 | declare_members_pass(source_file: SOURCE_FILE) -> bool is | |
| 789 | let flags = container.build_flags; | |
| 790 | let definition = source_file.definition; | |
| 791 | ||
| 792 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 793 | container | |
| 794 | .declare_members | |
| 795 | .apply(definition); | |
| 796 | ||
| 797 | return true; | |
| 798 | fi | |
| 799 | ||
| 800 | return false; | |
| 801 | si | |
| 802 | ||
| 803 | resolve_member_uses_pass(source_file: SOURCE_FILE) -> bool is | |
| 804 | let flags = container.build_flags; | |
| 805 | let definition = source_file.definition; | |
| 806 | ||
| 807 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 808 | container | |
| 809 | .resolve_member_uses | |
| 810 | .apply(definition); | |
| 811 | ||
| 812 | return true; | |
| 813 | fi | |
| 814 | ||
| 815 | return false; | |
| 816 | si | |
| 817 | ||
| 818 | resolve_ancestors_pass(source_file: SOURCE_FILE) -> bool is | |
| 819 | let flags = container.build_flags; | |
| 820 | let definition = source_file.definition; | |
| 821 | ||
| 822 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 823 | container | |
| 824 | .resolve_ancestors | |
| 825 | .apply(definition); | |
| 826 | ||
| 827 | return true; | |
| 828 | fi | |
| 829 | ||
| 830 | return false; | |
| 831 | si | |
| 832 | ||
| 833 | resolve_type_expressions_pass(source_file: SOURCE_FILE) -> bool is | |
| 834 | let flags = container.build_flags; | |
| 835 | let definition = source_file.definition; | |
| 836 | ||
| 837 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 838 | container | |
| 839 | .resolve_type_expressions | |
| 840 | .apply(definition); | |
| 841 | ||
| 842 | return true; | |
| 843 | fi | |
| 844 | ||
| 845 | return false; | |
| 846 | si | |
| 847 | ||
| 848 | resolve_explicit_types_pass(source_file: SOURCE_FILE) -> bool is | |
| 849 | let flags = container.build_flags; | |
| 850 | let definition = source_file.definition; | |
| 851 | ||
| 852 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 853 | container | |
| 854 | .resolve_explicit_types | |
| 855 | .apply(definition); | |
| 856 | ||
| 857 | return true; | |
| 858 | fi | |
| 859 | ||
| 860 | return false; | |
| 861 | si | |
| 862 | ||
| 863 | check_type_argument_bounds_pass(source_file: SOURCE_FILE) -> bool is | |
| 864 | let flags = container.build_flags; | |
| 865 | let definition = source_file.definition; | |
| 866 | ||
| 867 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 868 | container | |
| 869 | .check_type_argument_bounds | |
| 870 | .apply(definition); | |
| 871 | ||
| 872 | return true; | |
| 873 | fi | |
| 874 | ||
| 875 | return false; | |
| 876 | si | |
| 877 | ||
| 878 | compile_expressions_pass(source_file: SOURCE_FILE) -> bool is | |
| 879 | let definition = source_file.definition; | |
| 880 | ||
| 881 | if source_file.want_compile_expressions then | |
| 882 | container | |
| 883 | .compile_expressions | |
| 884 | .apply(definition); | |
| 885 | ||
| 886 | return true; | |
| 887 | fi | |
| 888 | ||
| 889 | return false; | |
| 890 | si | |
| 891 | ||
| 892 | mark_boxed_locals_pass(source_file: SOURCE_FILE) -> bool is | |
| 893 | let flags = container.build_flags; | |
| 894 | let definition = source_file.definition; | |
| 895 | ||
| 896 | // Boxing only matters once the IR is lowered to IL: | |
| 897 | // assignability of a captured local is keyed on its | |
| 898 | // mut marker alone, so analysis-only builds skip | |
| 899 | // deriving the boxing flags. Within an IL-bound | |
| 900 | // build, pair the gate with compile-expressions - | |
| 901 | // if the file isn't being walked for expression-level | |
| 902 | // semantics this run, no downstream consumer would | |
| 903 | // see the `is_boxed` flags we'd set. | |
| 904 | if | |
| 905 | source_file.want_compile_expressions /\ | |
| 906 | (flags.want_assembler \/ flags.want_executable) | |
| 907 | then | |
| 908 | container | |
| 909 | .mark_boxed_locals | |
| 910 | .apply(definition); | |
| 911 | ||
| 912 | return true; | |
| 913 | fi | |
| 914 | ||
| 915 | return false; | |
| 916 | si | |
| 917 | ||
| 918 | resolve_overrides_pass(source_file: SOURCE_FILE) -> bool is | |
| 919 | let definition = source_file.definition; | |
| 920 | ||
| 921 | if source_file.want_compile_up_to_expressions then | |
| 922 | container | |
| 923 | .resolve_overrides | |
| 924 | .apply(definition); | |
| 925 | ||
| 926 | return true; | |
| 927 | fi | |
| 928 | ||
| 929 | return false; | |
| 930 | si | |
| 931 | ||
| 932 | infer_store_free_pass(source_file: SOURCE_FILE) -> bool is | |
| 933 | let definition = source_file.definition; | |
| 934 | ||
| 935 | if source_file.want_compile_up_to_expressions then | |
| 936 | let infer_store_free = container.infer_store_free; | |
| 937 | ||
| 938 | // A file whose tree and bindings are unchanged since its | |
| 939 | // facts were last derived contributes them as-is; only | |
| 940 | // stale files pay the body re-walk. The fixpoint always | |
| 941 | // runs over the whole run's union, so cross-file | |
| 942 | // propagation is unaffected. | |
| 943 | if let retained_facts = source_file.store_free_facts then | |
| 944 | infer_store_free.absorb(retained_facts); | |
| 945 | ||
| 946 | return true; | |
| 947 | fi | |
| 948 | ||
| 949 | infer_store_free.begin_file_bucket(); | |
| 950 | ||
| 951 | infer_store_free.apply(definition); | |
| 952 | ||
| 953 | let bucket = infer_store_free.end_file_bucket(); | |
| 954 | ||
| 955 | // an abandoned walk's partial facts stay in this run's | |
| 956 | // union (conservative) but are not retained for reuse | |
| 957 | if !infer_store_free.last_walk_failed then | |
| 958 | source_file.store_free_facts = bucket; | |
| 959 | fi | |
| 960 | ||
| 961 | return true; | |
| 962 | fi | |
| 963 | ||
| 964 | return false; | |
| 965 | si | |
| 966 | ||
| 967 | register_source_intrinsics_pass(source_file: SOURCE_FILE) -> bool is | |
| 968 | container | |
| 969 | .register_source_intrinsics | |
| 970 | .apply(source_file.definition); | |
| 971 | ||
| 972 | return true; | |
| 973 | si | |
| 974 | ||
| 975 | check_pure_overrides_pass(source_file: SOURCE_FILE) -> bool is | |
| 976 | let definition = source_file.definition; | |
| 977 | ||
| 978 | if source_file.want_compile_up_to_expressions then | |
| 979 | container | |
| 980 | .check_pure_overrides | |
| 981 | .apply(definition); | |
| 982 | ||
| 983 | return true; | |
| 984 | fi | |
| 985 | ||
| 986 | return false; | |
| 987 | si | |
| 988 | ||
| 989 | record_type_argument_uses_pass(source_file: SOURCE_FILE) -> bool is | |
| 990 | let flags = container.build_flags; | |
| 991 | ||
| 992 | if flags.want_assembler \/ flags.want_executable then | |
| 993 | let definition = source_file.definition; | |
| 994 | ||
| 995 | container | |
| 996 | .record_type_argument_uses | |
| 997 | .apply(definition); | |
| 998 | ||
| 999 | return true; | |
| 1000 | fi | |
| 1001 | ||
| 1002 | return false; | |
| 1003 | si | |
| 1004 | ||
| 1005 | generate_il_pass(source_file: SOURCE_FILE) -> bool is | |
| 1006 | let flags = container.build_flags; | |
| 1007 | ||
| 1008 | if flags.want_assembler \/ flags.want_executable then | |
| 1009 | // FIXME why are we doing this? | |
| 1010 | container | |
| 1011 | .ir_context | |
| 1012 | .throw_on_fixme = false; | |
| 1013 | ||
| 1014 | let definition = source_file.definition; | |
| 1015 | ||
| 1016 | container | |
| 1017 | .generate_il | |
| 1018 | .apply(definition); | |
| 1019 | ||
| 1020 | return true; | |
| 1021 | fi | |
| 1022 | ||
| 1023 | return false; | |
| 1024 | si | |
| 1025 | si | |
| 1026 | si |