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 | _verify_compile_state_had_errors: bool | |
| 25 | ||
| 26 | is_full_compile_needed: bool public | |
| 27 | ||
| 28 | // True when the declaration-level state - symbols, ancestries, | |
| 29 | // override links, store-free bits - reflects every registered | |
| 30 | // file's current source: set by a completed build, cleared the | |
| 31 | // moment clear_symbols abandons the tables. Distinct from | |
| 32 | // is_full_compile_needed, which tracks whether expression-level | |
| 33 | // DIAGNOSTICS are stale for some files; right after an | |
| 34 | // interface-affecting EDIT's rebuild the tables are current while | |
| 35 | // a full compile is still needed. | |
| 36 | are_tables_current: bool public | |
| 37 | ||
| 38 | // Called at each per-file boundary of the compile-expressions | |
| 39 | // walk, if anything is interested. Analysis mode uses it to | |
| 40 | // answer queries that arrived while a compile was running; a | |
| 41 | // batch build leaves it unset. | |
| 42 | // | |
| 43 | // The boundary is where a file has just been walked and both the | |
| 44 | // scope stack and the namespace stack are back at their base - | |
| 45 | // the only point during a compile at which the retained state is | |
| 46 | // as consistent as it is between builds. | |
| 47 | on_file_boundary: (() -> void)? public | |
| 48 | ||
| 49 | generated_source_files: MutableList[string] | |
| 50 | ||
| 51 | _effects_stopwatch: System.Diagnostics.Stopwatch? | |
| 52 | ||
| 53 | // See the build() overload below. | |
| 54 | _want_entry_point_selection: bool | |
| 55 | ||
| 56 | // Files with a body walked since the effect relations were last | |
| 57 | // solved: what the next solve has to re-derive before it can | |
| 58 | // answer for them. | |
| 59 | _effects_stale_files: Collections.SET[string] | |
| 60 | ||
| 61 | init() is | |
| 62 | container = IoC.CONTAINER.instance | |
| 63 | logger = container.logger | |
| 64 | _effects_stale_files = Collections.SET[string]() | |
| 65 | ||
| 66 | // Nothing has been compiled yet, so expression-level state is | |
| 67 | // certainly absent: a compile request against this fresh state | |
| 68 | // has to run the full build, not the incremental effects pass, | |
| 69 | // which assumes the symbol tables exist (in particular the | |
| 70 | // namespaces only `build()` creates). | |
| 71 | is_full_compile_needed = true | |
| 72 | ||
| 73 | source_files = LIST[SOURCE_FILE]() | |
| 74 | generated_source_files = LIST[string]() | |
| 75 | ||
| 76 | post_parse_passes = LIST[Pass]() | |
| 77 | add_pass(post_parse_passes, "conditional-compilation", (source_file) -> bool => conditional_compilation_pass(source_file)) | |
| 78 | add_pass(post_parse_passes, "collect-modifier-keyword-locations", (source_file) -> bool => collect_modifier_keyword_locations_pass(source_file)) | |
| 79 | add_pass(post_parse_passes, "rewrite-syntax-trees", (source_file) -> bool => rewrite_syntax_tree_pass(source_file)) | |
| 80 | add_pass(post_parse_passes, "collect-suppress-pragmas", (source_file) -> bool => collect_suppress_pragmas_pass(source_file)) | |
| 81 | ||
| 82 | build_passes = LIST[Pass]() | |
| 83 | // Declares the type-level skeleton only: namespaces, types, | |
| 84 | // variants and type parameters - everything a type expression | |
| 85 | // can name. Members are declared by declare-members below. | |
| 86 | add_pass(build_passes, "declare-symbols", (source_file) -> bool => declare_symbols_pass(source_file)) | |
| 87 | // First round: binds imports of namespaces and types. Imports | |
| 88 | // of members bind in resolve-member-uses, once they exist. | |
| 89 | add_pass(build_passes, "resolve-uses", (source_file) -> bool => resolve_uses_pass(source_file)) | |
| 90 | // Runs after resolve-uses so a member - in particular an | |
| 91 | // impl/partial block's target name - can be reached through a | |
| 92 | // use import and declared anywhere relative to its uses. | |
| 93 | add_pass(build_passes, "declare-members", (source_file) -> bool => declare_members_pass(source_file)) | |
| 94 | // Second round: binds imports of members - static methods, | |
| 95 | // global functions, enum members - and reports anything still | |
| 96 | // unresolved. | |
| 97 | add_pass(build_passes, "resolve-member-uses", (source_file) -> bool => resolve_member_uses_pass(source_file)) | |
| 98 | // Runs after declare-members so it can read each class's | |
| 99 | // computed `is_abstract` (explicit + implicit) rather than | |
| 100 | // just the syntactic modifier. | |
| 101 | // Walks every file to report warnings but leaves nothing a | |
| 102 | // later pass reads, so it reports its walk without advancing | |
| 103 | // the compiled_through marker. | |
| 104 | add_pass(build_passes, "check-name-conventions", (source_file) -> bool => check_name_conventions_pass(source_file)) | |
| 105 | .advances_milestone = false | |
| 106 | add_pass(build_passes, "resolve-type-expressions", (source_file: SOURCE_FILE) -> bool => resolve_type_expressions_pass(source_file)) | |
| 107 | add_pass(build_passes, "resolve-ancestors", (source_file) -> bool => resolve_ancestors_pass(source_file)) | |
| 108 | add_pass(build_passes, "synthesize-class-equality", (source_file) -> bool => synthesize_class_equality_pass(source_file)) | |
| 109 | add_pass(build_passes, "synthesize-iterator-reset", (source_file) -> bool => synthesize_iterator_reset_pass(source_file)) | |
| 110 | add_pass(build_passes, "resolve-explicit-types", (source_file) -> bool => resolve_explicit_types_pass(source_file)) | |
| 111 | add_pass(build_passes, "check-type-argument-bounds", (source_file) -> bool => check_type_argument_bounds_pass(source_file)) | |
| 112 | add_pass( | |
| 113 | build_passes, | |
| 114 | "resolve-overrides", | |
| 115 | () -> void is si, | |
| 116 | (source_file) -> bool => resolve_overrides_pass(source_file), | |
| 117 | () -> void is | |
| 118 | container.resolve_overrides.check_duplicate_global_functions() | |
| 119 | si | |
| 120 | ) | |
| 121 | // Editor-only: announces each declaration's dispatch state | |
| 122 | // once every file's overrides have been resolved. Reports | |
| 123 | // nothing a later pass reads, so it does not advance the | |
| 124 | // milestone. | |
| 125 | add_pass(build_passes, "definition-virtuality", (source_file) -> bool => definition_virtuality_pass(source_file)) | |
| 126 | .advances_milestone = false | |
| 127 | add_pass(build_passes, "register-source-intrinsics", (source_file) -> bool => register_source_intrinsics_pass(source_file)) | |
| 128 | add_pass(build_passes, "record-type-argument-uses", (source_file) -> bool => record_type_argument_uses_pass(source_file)) | |
| 129 | ||
| 130 | add_pass(build_passes, "mark-boxed-locals", (source_file) -> bool => mark_boxed_locals_pass(source_file)) | |
| 131 | ||
| 132 | _compile_expressions_pass = add_pass( | |
| 133 | build_passes, | |
| 134 | "compile-expressions", | |
| 135 | () -> void is si, | |
| 136 | (source_file) -> bool => compile_expressions_pass(source_file), | |
| 137 | () -> void is si | |
| 138 | ) | |
| 139 | ||
| 140 | // Re-walks every body with the types compile-expressions | |
| 141 | // resolved, then solves the member-granular effect | |
| 142 | // relations the crossing discharge reads. Reports nothing a | |
| 143 | // later pass consumes through the milestone, so it does not | |
| 144 | // advance it. | |
| 145 | add_pass( | |
| 146 | build_passes, | |
| 147 | "infer-effects", | |
| 148 | () -> void is | |
| 149 | _effects_stopwatch = System.Diagnostics.Stopwatch.start_new() | |
| 150 | ||
| 151 | Syntax.Process.EFFECT_FACTS.collecting = true | |
| 152 | Syntax.Process.EFFECT_FACTS.begin_generation("infer-effects") | |
| 153 | ||
| 154 | container.infer_store_free.set_resolved_mode(true) | |
| 155 | container.infer_store_free.start_run() | |
| 156 | si, | |
| 157 | (source_file) -> bool => infer_effects_pass(source_file), | |
| 158 | () -> void is | |
| 159 | let stopwatch = _effects_stopwatch | |
| 160 | let walk_ms = if stopwatch? then stopwatch.elapsed_milliseconds else 0L fi | |
| 161 | ||
| 162 | let complete = _all_compiled_through_expressions(source_files) | |
| 163 | ||
| 164 | container.infer_store_free.solve_effects(complete) | |
| 165 | container.infer_store_free.set_resolved_mode(false) | |
| 166 | ||
| 167 | Syntax.Process.EFFECT_FACTS.collecting = false | |
| 168 | ||
| 169 | let solve_ms = if stopwatch? then stopwatch.elapsed_milliseconds else 0L fi | |
| 170 | ||
| 171 | // Re-walk the files whose walk disagrees with the | |
| 172 | // relations until a solve finds none, then judge | |
| 173 | // and check against the final relations. | |
| 174 | run_combined_solve_rounds(source_files) | |
| 175 | ||
| 176 | let rounds_ms = if stopwatch? then stopwatch.elapsed_milliseconds else 0L fi | |
| 177 | ||
| 178 | run_post_effects_purity_checks(source_files) | |
| 179 | ||
| 180 | Syntax.Process.RETRY_STATS.report() | |
| 181 | Syntax.Process.RETRY_SITE_STATS.report() | |
| 182 | ||
| 183 | _dump_kills() | |
| 184 | ||
| 185 | if System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? /\ stopwatch? then | |
| 186 | IO.Std.error.write_line("effects round: {stopwatch.elapsed_milliseconds} ms (walk {walk_ms} solve {solve_ms - walk_ms} rounds {rounds_ms - solve_ms} judge {stopwatch.elapsed_milliseconds - rounds_ms})") | |
| 187 | fi | |
| 188 | si | |
| 189 | ).advances_milestone = false | |
| 190 | ||
| 191 | // After the effect rounds have re-walked whatever they were | |
| 192 | // going to, so the state checked is the state generate-il | |
| 193 | // will read. Whether the build had errors is read once, before | |
| 194 | // any file is checked: a violation is itself an error, and | |
| 195 | // reading the live count would stop the check at the first | |
| 196 | // file that reports one. | |
| 197 | add_pass( | |
| 198 | build_passes, | |
| 199 | "verify-compile-state", | |
| 200 | () -> void is _verify_compile_state_had_errors = logger.any_errors si, | |
| 201 | (source_file) -> bool => verify_compile_state_pass(source_file), | |
| 202 | () -> void is si | |
| 203 | ).advances_milestone = false | |
| 204 | ||
| 205 | // Before generate-il, so the choice - and the diagnostics it | |
| 206 | // produces - are made by a pass that also runs in analysis | |
| 207 | // mode, and after compile-expressions, so a candidate's | |
| 208 | // inferred return type is settled when its signature is | |
| 209 | // checked. | |
| 210 | add_pass( | |
| 211 | build_passes, | |
| 212 | "select-entry-point", | |
| 213 | () -> void is | |
| 214 | if _want_entry_point_selection then | |
| 215 | container.select_entry_point.start() | |
| 216 | fi | |
| 217 | si, | |
| 218 | (source_file) -> bool => select_entry_point_pass(source_file), | |
| 219 | () -> void is | |
| 220 | if _want_entry_point_selection then | |
| 221 | container.select_entry_point.finish() | |
| 222 | fi | |
| 223 | si | |
| 224 | ).advances_milestone = false | |
| 225 | ||
| 226 | add_pass( | |
| 227 | build_passes, | |
| 228 | "generate-il", | |
| 229 | () -> void is | |
| 230 | if !is_emitting_assembly then | |
| 231 | return | |
| 232 | fi | |
| 233 | ||
| 234 | let srm_emitter = container.ir_context.srm_assembly_emitter | |
| 235 | ||
| 236 | srm_emitter.referenced_assemblies = container.referenced_assemblies | |
| 237 | ||
| 238 | // Not at the point the emitter is constructed: the | |
| 239 | // constructor signature these reference is built from | |
| 240 | // the string type, which the symbol table does not | |
| 241 | // hold until the reflected assemblies have been read. | |
| 242 | container.assembly_info.gen(srm_emitter) | |
| 243 | ||
| 244 | Semantic.DotNet.SESSION_CELL_ATTRIBUTES.gen( | |
| 245 | srm_emitter, | |
| 246 | container.build_flags.submission_name, | |
| 247 | container.assemblies) | |
| 248 | ||
| 249 | // Assign every definition its metadata row before any | |
| 250 | // body is encoded, so a body can name a target whose | |
| 251 | // row is not written yet. | |
| 252 | IR.Emitter.SRM_STRUCTURE_WALK(srm_emitter) | |
| 253 | .number(container.symbol_table.global_scope) | |
| 254 | si, | |
| 255 | (source_file) -> bool => generate_il_pass(source_file), | |
| 256 | () -> void is | |
| 257 | if !is_emitting_assembly then | |
| 258 | return | |
| 259 | fi | |
| 260 | ||
| 261 | let srm_emitter = container.ir_context.srm_assembly_emitter | |
| 262 | ||
| 263 | // Write the rows the numbering pass promised, now | |
| 264 | // that every body has been encoded and deposited. | |
| 265 | // Driven by the sequence that pass recorded, not by | |
| 266 | // the symbol table, which has moved on since. | |
| 267 | if !container.logger.any_errors then | |
| 268 | IR.Emitter.SRM_STRUCTURE_WALK(srm_emitter) | |
| 269 | .write_rows() | |
| 270 | fi | |
| 271 | ||
| 272 | // An executable build that named no entry point would | |
| 273 | // otherwise be written out and fail only when something | |
| 274 | // tried to run it. Surface it as a located compiler | |
| 275 | // error instead; `finish_build` aborts on `any_errors` | |
| 276 | // before the image is written. Library builds | |
| 277 | // (`--library`) legitimately have no entry point. Gated | |
| 278 | // on no prior errors so it doesn't pile onto a build | |
| 279 | // already failing for another reason (an error-aborted | |
| 280 | // build never reaches `.entrypoint` emission, so | |
| 281 | // `seen_entrypoint` is false regardless of whether the | |
| 282 | // source declares an `entry`). | |
| 283 | let flags = container.build_flags | |
| 284 | ||
| 285 | if flags.want_executable /\ | |
| 286 | !flags.want_library /\ | |
| 287 | !container.ir_context.seen_entrypoint /\ | |
| 288 | !container.logger.any_errors | |
| 289 | then | |
| 290 | let message = | |
| 291 | if container.select_entry_point.named_entry_point_not_found then | |
| 292 | "no function matches the --entry name {container.ir_context.entry_point_name}" | |
| 293 | elif container.select_entry_point.left_top_level_statements_unrun then | |
| 294 | "no entry point selected; name one with --entry, or leave one file carrying top-level statements" | |
| 295 | else | |
| 296 | "no entry point declared; add an 'entry()' function or build with --library" | |
| 297 | fi | |
| 298 | ||
| 299 | container.logger.error(Source.LOCATION.internal, message) | |
| 300 | fi | |
| 301 | si | |
| 302 | ) | |
| 303 | ||
| 304 | // Pass.order is set in registration order so a SOURCE_FILE's | |
| 305 | // compiled_through.order can be compared numerically against the | |
| 306 | // order of a target pass (e.g. _compile_expressions_pass). Post- | |
| 307 | // parse passes get their own counter starting at zero and don't | |
| 308 | // participate in compiled_through queries. | |
| 309 | assign_pass_orders(post_parse_passes) | |
| 310 | assign_pass_orders(build_passes) | |
| 311 | si | |
| 312 | ||
| 313 | assign_pass_orders(passes: Collections.Iterable[Pass]) static is | |
| 314 | let order mut = 0 | |
| 315 | for p in passes do | |
| 316 | p.order = order | |
| 317 | order = order + 1 | |
| 318 | od | |
| 319 | si | |
| 320 | ||
| 321 | dump_counts() is | |
| 322 | container.namespaces.dump_counts() | |
| 323 | container.symbol_use_locations.dump_counts() | |
| 324 | container.symbol_definition_locations.dump_counts() | |
| 325 | si | |
| 326 | ||
| 327 | clear_symbols() is | |
| 328 | clear_symbols(Collections.LIST[string]()) | |
| 329 | si | |
| 330 | ||
| 331 | // Abandon the symbol tables ahead of a rebuild. Every source | |
| 332 | // symbol is re-created; those of a file that was not edited are | |
| 333 | // re-declared from its retained tree in the same order as | |
| 334 | // before, and adopt the ids they had, so everything keyed by | |
| 335 | // symbol id - the solved effect relations above all - carries | |
| 336 | // over. Symbols of an edited file get fresh ids. | |
| 337 | clear_symbols(edited_files: Collections.Iterable[string]) is | |
| 338 | are_tables_current = false | |
| 339 | ||
| 340 | // The retained effect facts describe the symbols being | |
| 341 | // abandoned, and what the solve reads off a symbol - its | |
| 342 | // owner's openness, its overriders, its purity - can change | |
| 343 | // with the rebuild; the next solve starts from every body. | |
| 344 | container.infer_store_free.start_run() | |
| 345 | _effects_stale_files.clear() | |
| 346 | ||
| 347 | container.symbol_table.clear() | |
| 348 | container.namespaces.clear() | |
| 349 | container.symbol_use_locations.clear() | |
| 350 | container.symbol_definition_locations.clear_and_replay_ids(edited_files) | |
| 351 | Semantic.Symbols.STORE_FREE_BITS.clear() | |
| 352 | ||
| 353 | // The emitter's rows and reference caches name symbols that | |
| 354 | // have just been abandoned, and analysis rebuilds on every | |
| 355 | // edit: kept, they accumulate for the session's lifetime. | |
| 356 | container.ir_context.reset_srm_assembly_emitter() | |
| 357 | // container.logger.clear(); | |
| 358 | ||
| 359 | // Every previously-queued file's expression-level state now | |
| 360 | // references symbols that have just been abandoned. Drop the | |
| 361 | // marker so query handlers can tell. | |
| 362 | for f in source_files do | |
| 363 | f.compiled_through = null | |
| 364 | od | |
| 365 | si | |
| 366 | ||
| 367 | is_compiled_through_expressions(source_file: SOURCE_FILE) -> bool => | |
| 368 | let compiled_through = source_file?.compiled_through in | |
| 369 | compiled_through? /\ | |
| 370 | compiled_through.order >= _compile_expressions_pass.order | |
| 371 | ||
| 372 | clear_queue() is | |
| 373 | source_files.clear() | |
| 374 | si | |
| 375 | ||
| 376 | add_pass(passes: Collections.MutableList[Pass], description: string, apply: SOURCE_FILE -> bool) -> Pass is | |
| 377 | let pass = PASS(container.timers, description, null, apply, null) | |
| 378 | passes.add(pass) | |
| 379 | return pass | |
| 380 | si | |
| 381 | ||
| 382 | add_pass( | |
| 383 | passes: Collections.MutableList[Pass], | |
| 384 | description: string, | |
| 385 | start: () -> void, | |
| 386 | apply: SOURCE_FILE -> bool, | |
| 387 | finish: () -> void | |
| 388 | ) -> Pass | |
| 389 | is | |
| 390 | let pass = PASS(container.timers, description, start, apply, finish) | |
| 391 | passes.add(pass) | |
| 392 | return pass | |
| 393 | si | |
| 394 | ||
| 395 | add_pass(passes: Collections.MutableList[Pass], pass: Pass) is | |
| 396 | passes.add(pass) | |
| 397 | si | |
| 398 | ||
| 399 | parse_and_queue(path: string, reader: IO.TextReader, want_compile_up_to_expressions: bool, want_compile_expressions: bool, is_internal_file: bool) is | |
| 400 | queue( | |
| 401 | parse(path, reader, want_compile_up_to_expressions, want_compile_expressions, is_internal_file) | |
| 402 | ) | |
| 403 | si | |
| 404 | ||
| 405 | queue(source_file: SOURCE_FILE) is | |
| 406 | source_files.add(source_file) | |
| 407 | si | |
| 408 | ||
| 409 | queue(source_files: Collections.Iterable[SOURCE_FILE]) is | |
| 410 | self.source_files.add_range(source_files) | |
| 411 | si | |
| 412 | ||
| 413 | parse(path: string, reader: IO.TextReader, want_compile_up_to_expressions: bool, want_compile_expressions: bool, is_internal_file: bool) -> SOURCE_FILE => | |
| 414 | parse(path, reader, want_compile_up_to_expressions, want_compile_expressions, is_internal_file, logger) | |
| 415 | ||
| 416 | // Parsing into an explicitly-supplied logger lets a caller (the | |
| 417 | // analysis-mode format handlers) parse without touching the shared | |
| 418 | // diagnostics store: a throwaway logger keeps parse diagnostics and | |
| 419 | // the speculation state stack isolated from the live analyser state. | |
| 420 | parse( | |
| 421 | path: string, | |
| 422 | reader: IO.TextReader, | |
| 423 | want_compile_up_to_expressions: bool, | |
| 424 | want_compile_expressions: bool, | |
| 425 | is_internal_file: bool, | |
| 426 | logger: Logger | |
| 427 | ) -> SOURCE_FILE is | |
| 428 | let tokenizer = Lexical.TOKENIZER( | |
| 429 | logger, | |
| 430 | path, | |
| 431 | reader, | |
| 432 | is_internal_file | |
| 433 | ) | |
| 434 | ||
| 435 | // Speculative-lookahead ring buffer; a power of 2 (the | |
| 436 | // queue masks indices with `size - 1`), sized generously so | |
| 437 | // deep-but-finite speculation does not overflow it. A true | |
| 438 | // speculation loop is caught separately by the tokenizer's | |
| 439 | // backtrack-loop detector. | |
| 440 | let token_queue = Lexical.TOKEN_QUEUE(2048) | |
| 441 | ||
| 442 | let token_lookahead = Lexical.TOKEN_LOOKAHEAD( | |
| 443 | token_queue, | |
| 444 | tokenizer | |
| 445 | ) | |
| 446 | ||
| 447 | let context = Syntax.Parsers.CONTEXT( | |
| 448 | token_lookahead, | |
| 449 | logger | |
| 450 | ) | |
| 451 | ||
| 452 | // The file root parses as a definition list at namespace depth 0, | |
| 453 | // where a file with no namespace may carry bare top-level | |
| 454 | // statements (collected into a synthesised entry point). | |
| 455 | let definitions = container.definition_global_list_parser.parse(context)! | |
| 456 | ||
| 457 | let result = | |
| 458 | SOURCE_FILE( | |
| 459 | want_compile_up_to_expressions, | |
| 460 | want_compile_expressions, | |
| 461 | path, | |
| 462 | definitions | |
| 463 | ) | |
| 464 | ||
| 465 | result.trivia = tokenizer.trivia | |
| 466 | ||
| 467 | return result | |
| 468 | si | |
| 469 | ||
| 470 | // Parses one file and nothing more, into a logger of its own, and says | |
| 471 | // whether it is complete, stops before its end, or is wrong. A front | |
| 472 | // end reading a program a line at a time asks this to decide whether | |
| 473 | // to run what it has or to wait for another line. | |
| 474 | check_complete(path: string, reader: IO.TextReader) -> Syntax.Parsers.COMPLETENESS => | |
| 475 | check(path, reader).completeness | |
| 476 | ||
| 477 | // As check_complete, and what the last thing at the file's root is, | |
| 478 | // as Syntax.Parsers.LAST_STATEMENT_KIND names it. | |
| 479 | check(path: string, reader: IO.TextReader) -> (completeness: Syntax.Parsers.COMPLETENESS, last_statement: string?) is | |
| 480 | let completeness_logger = Logging.DIAGNOSTICS_STORE() | |
| 481 | ||
| 482 | let tokenizer = Lexical.TOKENIZER(completeness_logger, path, reader, false) | |
| 483 | ||
| 484 | let context = | |
| 485 | Syntax.Parsers.CONTEXT( | |
| 486 | Lexical.TOKEN_LOOKAHEAD(Lexical.TOKEN_QUEUE(2048), tokenizer), | |
| 487 | completeness_logger | |
| 488 | ) | |
| 489 | ||
| 490 | let root = container.definition_global_list_parser.parse(context) | |
| 491 | ||
| 492 | let errors = LIST[Source.LOCATION]() | |
| 493 | ||
| 494 | for d in completeness_logger.diagnostics_for(path) do | |
| 495 | if d.severity == DiagnosticSeverity.ERROR then | |
| 496 | errors.add(d.location) | |
| 497 | fi | |
| 498 | od | |
| 499 | ||
| 500 | let end_of_input_errors = LIST[Source.LOCATION]() | |
| 501 | ||
| 502 | end_of_input_errors.add_range(context.end_of_input_error_locations) | |
| 503 | end_of_input_errors.add_range(tokenizer.end_of_input_error_locations) | |
| 504 | ||
| 505 | let completeness = Syntax.Parsers.COMPLETENESS_CLASSIFIER().classify(errors, end_of_input_errors) | |
| 506 | ||
| 507 | let last_statement = | |
| 508 | if completeness == Syntax.Parsers.COMPLETENESS.COMPLETE /\ root? then | |
| 509 | Syntax.Parsers.LAST_STATEMENT_KIND.of(root) | |
| 510 | else | |
| 511 | null | |
| 512 | fi | |
| 513 | ||
| 514 | return (completeness = completeness, last_statement = last_statement) | |
| 515 | si | |
| 516 | ||
| 517 | post_parse(source_files: Iterable[SOURCE_FILE]) is | |
| 518 | build(post_parse_passes, source_files) | |
| 519 | si | |
| 520 | ||
| 521 | post_parse() is | |
| 522 | build(post_parse_passes, source_files) | |
| 523 | si | |
| 524 | ||
| 525 | build() is | |
| 526 | build(true) | |
| 527 | si | |
| 528 | ||
| 529 | // `want_entry_point_selection` false leaves out the pass that | |
| 530 | // settles which function the assembly enters at. Its answer can | |
| 531 | // only change when the project's declarations do, and nothing an | |
| 532 | // editor shows between one debounced compile and the next depends | |
| 533 | // on it, so an edit-driven rebuild skips a whole-project walk it | |
| 534 | // would repeat on every keystroke. | |
| 535 | build(want_entry_point_selection: bool) is | |
| 536 | _want_entry_point_selection = want_entry_point_selection | |
| 537 | ||
| 538 | container.ghul_namespace_creator.create_namespaces() | |
| 539 | ||
| 540 | Syntax.Process.PURE_SLOTS.clear() | |
| 541 | Syntax.Process.KILL_LEDGER.clear_all() | |
| 542 | ||
| 543 | _move_top_level_entry_file_first() | |
| 544 | ||
| 545 | build(build_passes, source_files) | |
| 546 | ||
| 547 | are_tables_current = true | |
| 548 | si | |
| 549 | ||
| 550 | // The file carrying the synthesised top-level entry compiles | |
| 551 | // first: the entry's walk settles its top-level `let` variables' | |
| 552 | // inferred types, and under --global-namespace a function in any | |
| 553 | // other file can read them. | |
| 554 | _move_top_level_entry_file_first() is | |
| 555 | for i in 0..source_files.count do | |
| 556 | if Syntax.Process.SYNTHESISE_TOP_LEVEL_ENTRY.has_top_level_entry(source_files[i].definition) then | |
| 557 | if i > 0 then | |
| 558 | let f = source_files[i] | |
| 559 | ||
| 560 | source_files.remove_at(i) | |
| 561 | source_files.insert(0, f) | |
| 562 | fi | |
| 563 | ||
| 564 | return | |
| 565 | fi | |
| 566 | od | |
| 567 | si | |
| 568 | ||
| 569 | build(passes: Collections.Iterable[Pass], source_files: Collections.Iterable[SOURCE_FILE]) is | |
| 570 | let is_compiling_expressions mut = false | |
| 571 | ||
| 572 | for pass in passes do | |
| 573 | if pass == _compile_expressions_pass then | |
| 574 | is_compiling_expressions = true | |
| 575 | fi | |
| 576 | ||
| 577 | logger.set_is_compiling_expressions(is_compiling_expressions) | |
| 578 | ||
| 579 | pass.start() | |
| 580 | ||
| 581 | for i in source_files do | |
| 582 | let symbol_table = IoC.CONTAINER.instance.symbol_table | |
| 583 | let symbol_table_mark = symbol_table.mark_scope_stack() | |
| 584 | let diagnostics_mark = logger.mark() | |
| 585 | let symbol_uses_mark = container.symbol_use_locations.mark() | |
| 586 | ||
| 587 | let did_work mut = false | |
| 588 | ||
| 589 | try | |
| 590 | did_work = pass.apply(i) | |
| 591 | catch e: Exception | |
| 592 | logger.exception(diagnostics_mark, i.definition.location, e, "caught exception running pass {pass} on work item {i}") | |
| 593 | finally | |
| 594 | logger.release(diagnostics_mark) | |
| 595 | container.symbol_use_locations.release(symbol_uses_mark) | |
| 596 | symbol_table.release_scope_stack(symbol_table_mark) | |
| 597 | yrt | |
| 598 | ||
| 599 | if did_work then | |
| 600 | WORK_COUNTERS.file_pass(container.timers, pass.description) | |
| 601 | ||
| 602 | if pass.advances_milestone then | |
| 603 | i.compiled_through = pass | |
| 604 | fi | |
| 605 | fi | |
| 606 | ||
| 607 | IoC.CONTAINER.instance.namespaces.pop_all_namespaces() | |
| 608 | ||
| 609 | if pass == _compile_expressions_pass then | |
| 610 | _reached_file_boundary() | |
| 611 | fi | |
| 612 | od | |
| 613 | ||
| 614 | // Every method has now been walked, which is what the | |
| 615 | // constructor field check needs: a constructor is routinely | |
| 616 | // written before the helper it delegates to, so the helper's | |
| 617 | // summary does not exist until the pass is over. | |
| 618 | if pass == _compile_expressions_pass then | |
| 619 | container.compile_expressions.report_field_assignments() | |
| 620 | fi | |
| 621 | ||
| 622 | pass.finish() | |
| 623 | od | |
| 624 | si | |
| 625 | ||
| 626 | // On-demand expression compile for one file against the retained | |
| 627 | // symbol table, replaying only the pipeline's compile-expressions | |
| 628 | // step with the same per-file bookkeeping as the build loop. | |
| 629 | // Valid when the file has been built up to expressions in the | |
| 630 | // current symbol generation - declare/resolve state current, only | |
| 631 | // the expression-level walk missing. The analysis-mode query-miss | |
| 632 | // recompile takes this instead of a whole-project rebuild when no | |
| 633 | // interface change is pending. | |
| 634 | compile_expressions_only(source_file: SOURCE_FILE) is | |
| 635 | source_file.want_compile_expressions = true | |
| 636 | _effects_stale_files.add(source_file.file_name) | |
| 637 | ||
| 638 | // This walk is the only pass a file the client opened after | |
| 639 | // the last full build ever gets: the build ran the | |
| 640 | // declaration-level virtuality pass gated on the open-files | |
| 641 | // set, so a file that was closed then recorded none of its | |
| 642 | // carriers. Re-run it here, as the body re-walk does, so its | |
| 643 | // inlays arrive with the expression-level ones. | |
| 644 | container.definition_virtuality.apply(source_file) | |
| 645 | ||
| 646 | // Same reasoning again: a closed file recorded none of the | |
| 647 | // whole-project build's name-convention warnings either, and | |
| 648 | // nothing else re-runs that walk for it. | |
| 649 | container.check_name_conventions.apply(source_file) | |
| 650 | ||
| 651 | // Same reasoning: this walk is the file's first and only | |
| 652 | // expression-level walk, so the marking narrowing consults | |
| 653 | // has to happen here rather than having been done by the | |
| 654 | // pass the build skipped for a closed file. | |
| 655 | container.mark_boxed_locals.apply(source_file.definition, _want_boxing) | |
| 656 | ||
| 657 | logger.set_is_compiling_expressions(true) | |
| 658 | ||
| 659 | let symbol_table = container.symbol_table | |
| 660 | let symbol_table_mark = symbol_table.mark_scope_stack() | |
| 661 | let diagnostics_mark = logger.mark() | |
| 662 | let symbol_uses_mark = container.symbol_use_locations.mark() | |
| 663 | ||
| 664 | let did_work mut = false | |
| 665 | ||
| 666 | try | |
| 667 | did_work = _compile_expressions_pass.apply(source_file) | |
| 668 | catch e: Exception | |
| 669 | logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception compiling expressions on demand for {source_file}") | |
| 670 | finally | |
| 671 | logger.release(diagnostics_mark) | |
| 672 | container.symbol_use_locations.release(symbol_uses_mark) | |
| 673 | symbol_table.release_scope_stack(symbol_table_mark) | |
| 674 | ||
| 675 | container.namespaces.pop_all_namespaces() | |
| 676 | ||
| 677 | logger.set_is_compiling_expressions(false) | |
| 678 | yrt | |
| 679 | ||
| 680 | if did_work then | |
| 681 | WORK_COUNTERS.file_pass(container.timers, _compile_expressions_pass.description) | |
| 682 | ||
| 683 | source_file.compiled_through = _compile_expressions_pass | |
| 684 | fi | |
| 685 | ||
| 686 | // This file is the whole of the walk, so its constructors are | |
| 687 | // reported here rather than waiting for a build loop that may | |
| 688 | // never run - without this an on-demand recompile reports the | |
| 689 | // file differently from a whole-project build of the same | |
| 690 | // source. | |
| 691 | container.compile_expressions.report_field_assignments() | |
| 692 | ||
| 693 | _reached_file_boundary() | |
| 694 | si | |
| 695 | ||
| 696 | _reached_file_boundary() is | |
| 697 | let boundary = on_file_boundary | |
| 698 | ||
| 699 | if boundary? then | |
| 700 | boundary() | |
| 701 | fi | |
| 702 | si | |
| 703 | ||
| 704 | // Drop what compile-expressions left behind for one file and put | |
| 705 | // it back in the set that pass has yet to walk. A CE-output-only | |
| 706 | // clear (preserve_resolved_types) drops that output - including | |
| 707 | // the node-keyed stores - while leaving TypeExpression.type | |
| 708 | // intact, so the walk can run again without re-resolving (and | |
| 709 | // thus double-setting) the retained interface types. | |
| 710 | // | |
| 711 | // The caller either re-walks the file now, through | |
| 712 | // recompile_file_expressions, or leaves it for the next compile's | |
| 713 | // expressions-only pass to pick up. | |
| 714 | invalidate_file_expressions(source_file: SOURCE_FILE) is | |
| 715 | let ce_clear = Syntax.Process.CLEAR_STATE_VISITOR(true) | |
| 716 | ce_clear.apply(source_file.definition) | |
| 717 | ||
| 718 | container.state_store_registry.drop_file(source_file.file_name) | |
| 719 | ||
| 720 | source_file.compiled_through = null | |
| 721 | si | |
| 722 | ||
| 723 | // Recompile one file's expressions against the current symbol | |
| 724 | // table, keeping its resolved interface, so every body rebinds. | |
| 725 | recompile_file_expressions(source_file: SOURCE_FILE) is | |
| 726 | Syntax.Process.KILL_LEDGER.clear_for(source_file.file_name) | |
| 727 | ||
| 728 | invalidate_file_expressions(source_file) | |
| 729 | ||
| 730 | logger.clear_expression_diagnostics(source_file.file_name) | |
| 731 | Syntax.Process.PURE_SLOTS.clear_for(source_file.file_name) | |
| 732 | ||
| 733 | compile_expressions_only(source_file) | |
| 734 | si | |
| 735 | ||
| 736 | // Re-walk one definition's body against the current symbol table | |
| 737 | // and the current effect relations: what the previous walk of that | |
| 738 | // body left behind - its node state, its expression-level | |
| 739 | // diagnostics and inlays, its ledger records and candidates - is | |
| 740 | // dropped, and compile-expressions is driven over that body | |
| 741 | // alone. The interface, and every other body in the file, is | |
| 742 | // untouched. | |
| 743 | recompile_body_expressions(source_file: SOURCE_FILE, function: Syntax.Trees.Definitions.FUNCTION) is | |
| 744 | let file_name = source_file.file_name | |
| 745 | let span = function.location | |
| 746 | ||
| 747 | _effects_stale_files.add(file_name) | |
| 748 | ||
| 749 | Syntax.Process.KILL_LEDGER.clear_for(function) | |
| 750 | ||
| 751 | if let body = function.body then | |
| 752 | Syntax.Process.CLEAR_STATE_VISITOR(true).apply(body) | |
| 753 | fi | |
| 754 | ||
| 755 | logger.clear_expression_diagnostics_within(file_name, span) | |
| 756 | container.symbol_use_locations.drop_within(file_name, span) | |
| 757 | Syntax.Process.PURE_SLOTS.clear_within(file_name, span) | |
| 758 | ||
| 759 | let only = Collections.SET[Syntax.Trees.Definitions.FUNCTION]() | |
| 760 | only.add(function) | |
| 761 | ||
| 762 | logger.set_is_compiling_expressions(true) | |
| 763 | ||
| 764 | container.compile_expressions.reset_between_walks() | |
| 765 | ||
| 766 | let symbol_table = container.symbol_table | |
| 767 | let mark = symbol_table.mark_scope_stack() | |
| 768 | ||
| 769 | let rewalker = Syntax.Process.BODY_REWALKER( | |
| 770 | logger, | |
| 771 | symbol_table, | |
| 772 | container.namespaces, | |
| 773 | container.compile_expressions, | |
| 774 | only | |
| 775 | ) | |
| 776 | ||
| 777 | try | |
| 778 | source_file.definition.walk(rewalker) | |
| 779 | catch e: Exception | |
| 780 | logger.exception(function.location, e, "caught exception re-walking body in {source_file}") | |
| 781 | finally | |
| 782 | symbol_table.release_scope_stack(mark) | |
| 783 | container.namespaces.pop_all_namespaces() | |
| 784 | ||
| 785 | logger.set_is_compiling_expressions(false) | |
| 786 | yrt | |
| 787 | ||
| 788 | container.compile_expressions.report_field_assignments() | |
| 789 | si | |
| 790 | ||
| 791 | // Declare, resolve and compile just the definitions an append-only | |
| 792 | // interface edit added to the end of a file, against the retained | |
| 793 | // symbol table. The appended subtree is new - nothing referenced | |
| 794 | // it before this edit - so no retained symbol changes and no other | |
| 795 | // file's state is touched: each pass walks only the new nodes. | |
| 796 | // The new functions carry no proven store-free bit, which is | |
| 797 | // the sound not-proven default until the next effects solve. | |
| 798 | // Namespaces named by the new declarations | |
| 799 | // merge into the retained namespace map exactly as they would in | |
| 800 | // a full build. | |
| 801 | build_appended(source_file: SOURCE_FILE, new_definitions: Syntax.Trees.Definitions.LIST) is | |
| 802 | source_file.want_compile_up_to_expressions = true | |
| 803 | source_file.want_compile_expressions = true | |
| 804 | ||
| 805 | _run_appended_pass(source_file, "declare-symbols", () is container.declare_symbols.apply(new_definitions); si) | |
| 806 | _run_appended_pass(source_file, "resolve-uses", () is container.resolve_uses.apply(new_definitions); si) | |
| 807 | _run_appended_pass(source_file, "declare-members", () is container.declare_members.apply(new_definitions); si) | |
| 808 | _run_appended_pass(source_file, "resolve-member-uses", () is container.resolve_member_uses.apply(new_definitions); si) | |
| 809 | _run_appended_pass(source_file, "resolve-type-expressions", () is container.resolve_type_expressions.apply(new_definitions); si) | |
| 810 | _run_appended_pass(source_file, "resolve-ancestors", () is container.resolve_ancestors.apply(new_definitions); si) | |
| 811 | _run_appended_pass(source_file, "synthesize-class-equality", () is container.synthesize_class_equality.apply(new_definitions); si) | |
| 812 | _run_appended_pass(source_file, "synthesize-iterator-reset", () is container.synthesize_iterator_reset.apply(new_definitions); si) | |
| 813 | _run_appended_pass(source_file, "resolve-explicit-types", () is container.resolve_explicit_types.apply(new_definitions); si) | |
| 814 | _run_appended_pass(source_file, "check-type-argument-bounds", () is container.check_type_argument_bounds.apply(new_definitions); si) | |
| 815 | _run_appended_pass(source_file, "resolve-overrides", () is container.resolve_overrides.apply(new_definitions); si) | |
| 816 | ||
| 817 | // These two are whole-file, declaration-level walks that | |
| 818 | // only otherwise run as part of build_passes - the same gap | |
| 819 | // rewalk_bodies and compile_expressions_only close for their | |
| 820 | // own paths. Appended declarations get no other pass over | |
| 821 | // them for either, so a badly-named or virtuality-relevant | |
| 822 | // new member would go unflagged until the next full rebuild. | |
| 823 | container.check_name_conventions.apply(source_file) | |
| 824 | container.definition_virtuality.apply(source_file) | |
| 825 | ||
| 826 | _run_appended_pass(source_file, "mark-boxed-locals", () is container.mark_boxed_locals.apply(new_definitions, _want_boxing); si) | |
| 827 | ||
| 828 | logger.set_is_compiling_expressions(true) | |
| 829 | ||
| 830 | _run_appended_pass(source_file, "compile-expressions", () is container.compile_expressions.apply(new_definitions); si) | |
| 831 | ||
| 832 | logger.set_is_compiling_expressions(false) | |
| 833 | si | |
| 834 | ||
| 835 | // Declare and resolve a container's changed function members | |
| 836 | // against the retained symbol table, with the scope cursor | |
| 837 | // positioned inside the enclosing namespace and class scopes the | |
| 838 | // way the build loop's tree walk would have it. Split from the | |
| 839 | // expression walk so a class's pull-down can re-run between the | |
| 840 | // two: overrides and pulled-down members must reflect the new | |
| 841 | // member set before any body compiles against them. | |
| 842 | build_members_interface( | |
| 843 | source_file: SOURCE_FILE, | |
| 844 | enclosing: Collections.List[Syntax.Trees.Definitions.Definition], | |
| 845 | members: Syntax.Trees.Definitions.LIST | |
| 846 | ) is | |
| 847 | source_file.want_compile_up_to_expressions = true | |
| 848 | source_file.want_compile_expressions = true | |
| 849 | ||
| 850 | _run_positioned_pass(source_file, enclosing, "declare-members", () is container.declare_members.apply(members); si) | |
| 851 | _run_positioned_pass(source_file, enclosing, "resolve-type-expressions", () is container.resolve_type_expressions.apply(members); si) | |
| 852 | _run_positioned_pass(source_file, enclosing, "resolve-explicit-types", () is container.resolve_explicit_types.apply(members); si) | |
| 853 | _run_positioned_pass(source_file, enclosing, "check-type-argument-bounds", () is container.check_type_argument_bounds.apply(members); si) | |
| 854 | si | |
| 855 | ||
| 856 | build_members_expressions( | |
| 857 | source_file: SOURCE_FILE, | |
| 858 | enclosing: Collections.List[Syntax.Trees.Definitions.Definition], | |
| 859 | members: Syntax.Trees.Definitions.LIST | |
| 860 | ) is | |
| 861 | _run_positioned_pass(source_file, enclosing, "mark-boxed-locals", () is container.mark_boxed_locals.apply(members, _want_boxing); si) | |
| 862 | ||
| 863 | logger.set_is_compiling_expressions(true) | |
| 864 | ||
| 865 | _run_positioned_pass(source_file, enclosing, "compile-expressions", () is container.compile_expressions.apply(members); si) | |
| 866 | ||
| 867 | logger.set_is_compiling_expressions(false) | |
| 868 | ||
| 869 | // Same reasoning as build_appended: the replaced window's | |
| 870 | // members get no other pass over these two whole-file | |
| 871 | // declaration-level walks. Run here, after | |
| 872 | // build_members_interface's caller has re-pulled overrides | |
| 873 | // for the changed closure, so definition-virtuality's glyphs | |
| 874 | // reflect the reconciled dispatch state rather than the | |
| 875 | // pre-replace one. | |
| 876 | container.check_name_conventions.apply(source_file) | |
| 877 | container.definition_virtuality.apply(source_file) | |
| 878 | si | |
| 879 | ||
| 880 | // One pass application over a changed member subtree, with the | |
| 881 | // symbol-table and namespace cursors positioned inside the | |
| 882 | // enclosing scopes first - a namespace enters through its own | |
| 883 | // node's scope plus the namespace prefix stack, a class-like | |
| 884 | // definition through its own node's scope, mirroring | |
| 885 | // ScopedVisitor. The finally's release and pop restore both | |
| 886 | // cursors however deep the positioning got. | |
| 887 | _run_positioned_pass( | |
| 888 | source_file: SOURCE_FILE, | |
| 889 | enclosing: Collections.List[Syntax.Trees.Definitions.Definition], | |
| 890 | pass_name: string, | |
| 891 | apply_pass: () -> void | |
| 892 | ) is | |
| 893 | WORK_COUNTERS.subtree_pass(container.timers, pass_name) | |
| 894 | ||
| 895 | let symbol_table = container.symbol_table | |
| 896 | let symbol_table_mark = symbol_table.mark_scope_stack() | |
| 897 | let diagnostics_mark = logger.mark() | |
| 898 | let symbol_uses_mark = container.symbol_use_locations.mark() | |
| 899 | ||
| 900 | try | |
| 901 | for node in enclosing do | |
| 902 | if let namespace_definition: Syntax.Trees.Definitions.NAMESPACE = node then | |
| 903 | container.namespaces.enter_namespace(namespace_definition.name.location, namespace_definition.name.name) | |
| 904 | symbol_table.enter_scope(namespace_definition) | |
| 905 | elif let carrier: Syntax.Trees.ScopeCarrier = node then | |
| 906 | symbol_table.enter_scope(carrier) | |
| 907 | else | |
| 908 | logger.poison(node.location, "no scope found for {node.get_type()}") | |
| 909 | fi | |
| 910 | od | |
| 911 | ||
| 912 | apply_pass() | |
| 913 | catch e: Exception | |
| 914 | logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception building changed members in {source_file}") | |
| 915 | finally | |
| 916 | logger.release(diagnostics_mark) | |
| 917 | container.symbol_use_locations.release(symbol_uses_mark) | |
| 918 | symbol_table.release_scope_stack(symbol_table_mark) | |
| 919 | ||
| 920 | container.namespaces.pop_all_namespaces() | |
| 921 | yrt | |
| 922 | si | |
| 923 | ||
| 924 | // One pass application over an appended subtree, under the build | |
| 925 | // loop's per-file bookkeeping. | |
| 926 | _run_appended_pass(source_file: SOURCE_FILE, pass_name: string, apply_pass: () -> void) is | |
| 927 | WORK_COUNTERS.subtree_pass(container.timers, pass_name) | |
| 928 | ||
| 929 | let symbol_table = container.symbol_table | |
| 930 | let symbol_table_mark = symbol_table.mark_scope_stack() | |
| 931 | let diagnostics_mark = logger.mark() | |
| 932 | let symbol_uses_mark = container.symbol_use_locations.mark() | |
| 933 | ||
| 934 | try | |
| 935 | apply_pass() | |
| 936 | catch e: Exception | |
| 937 | logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception building appended declarations in {source_file}") | |
| 938 | finally | |
| 939 | logger.release(diagnostics_mark) | |
| 940 | container.symbol_use_locations.release(symbol_uses_mark) | |
| 941 | symbol_table.release_scope_stack(symbol_table_mark) | |
| 942 | ||
| 943 | container.namespaces.pop_all_namespaces() | |
| 944 | yrt | |
| 945 | si | |
| 946 | ||
| 947 | // Incremental body re-walk: re-process only the edited file's new | |
| 948 | // function bodies, after its donor bodies have been spliced in by the | |
| 949 | // analysis-mode EDIT handler. Every pass runs body-scoped — through | |
| 950 | // BODY_DECLARER for declare-symbols, BODY_REWALKER for the rest — so | |
| 951 | // the retained interface (arguments, signatures, indexer / property | |
| 952 | // parameters) is never re-processed. Re-resolving a retained, | |
| 953 | // already-typed argument would poison it ("set type twice"); the | |
| 954 | // bodies are leaves, so processing them alone is also sufficient. | |
| 955 | // resolve-uses / resolve-ancestors / resolve-overrides are skipped — | |
| 956 | // they do not visit body interiors and the interface is unchanged. | |
| 957 | // | |
| 958 | // Before the re-walk re-records the bodies, the edited file's stale | |
| 959 | // symbol-use / definition entries are reconciled: `correspondence` | |
| 960 | // gives every retained interface node's post-edit location, and | |
| 961 | // `body_spans` the pre-edit spans of the re-walked bodies. Body | |
| 962 | // entries are dropped (re-recorded here), interface entries and | |
| 963 | // symbols moved to their post-edit locations. | |
| 964 | rewalk_bodies( | |
| 965 | source_file: SOURCE_FILE, | |
| 966 | correspondence: Source.LOCATION_CORRESPONDENCE, | |
| 967 | body_spans: Source.BODY_SPANS | |
| 968 | ) is | |
| 969 | source_file.want_compile_up_to_expressions = true | |
| 970 | source_file.want_compile_expressions = true | |
| 971 | _effects_stale_files.add(source_file.file_name) | |
| 972 | ||
| 973 | Syntax.Process.KILL_LEDGER.clear_for(source_file.file_name) | |
| 974 | ||
| 975 | // Definition reconciliation first — it classifies symbols by | |
| 976 | // their pre-edit location, before the use reconciliation moves | |
| 977 | // the interface symbols. | |
| 978 | container.symbol_definition_locations.refresh_edited_file(source_file.file_name, body_spans) | |
| 979 | container.symbol_use_locations.refresh_edited_file(source_file.file_name, correspondence, body_spans) | |
| 980 | ||
| 981 | let flags = container.build_flags | |
| 982 | let symbol_table = container.symbol_table | |
| 983 | ||
| 984 | let body_declarer = Syntax.Process.BODY_DECLARER( | |
| 985 | logger, | |
| 986 | symbol_table, | |
| 987 | container.namespaces, | |
| 988 | container.declare_members | |
| 989 | ) | |
| 990 | ||
| 991 | // body-scoped declare-symbols | |
| 992 | WORK_COUNTERS.file_rewalk(container.timers, "declare-symbols") | |
| 993 | ||
| 994 | let mark = symbol_table.mark_scope_stack() | |
| 995 | let diagnostics_mark = logger.mark() | |
| 996 | ||
| 997 | try | |
| 998 | source_file.definition.walk(body_declarer) | |
| 999 | catch e: Exception | |
| 1000 | logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception declaring bodies in incremental re-walk of {source_file}") | |
| 1001 | finally | |
| 1002 | logger.release(diagnostics_mark) | |
| 1003 | symbol_table.release_scope_stack(mark) | |
| 1004 | container.namespaces.pop_all_namespaces() | |
| 1005 | yrt | |
| 1006 | ||
| 1007 | rewalk_pass(source_file, "resolve-type-expressions", container.resolve_type_expressions) | |
| 1008 | rewalk_pass(source_file, "synthesize-class-equality", container.synthesize_class_equality) | |
| 1009 | rewalk_pass(source_file, "synthesize-iterator-reset", container.synthesize_iterator_reset) | |
| 1010 | rewalk_pass(source_file, "resolve-explicit-types", container.resolve_explicit_types) | |
| 1011 | rewalk_pass(source_file, "check-type-argument-bounds", container.check_type_argument_bounds) | |
| 1012 | ||
| 1013 | if flags.want_assembler \/ flags.want_executable then | |
| 1014 | rewalk_pass(source_file, "record-type-argument-uses", container.record_type_argument_uses) | |
| 1015 | fi | |
| 1016 | ||
| 1017 | // The edited file's declaration-level diagnostics were | |
| 1018 | // dropped along with everything else before the re-walk, and | |
| 1019 | // no whole-project pass runs to replace them. This one is a | |
| 1020 | // per-file declaration walk over symbols an | |
| 1021 | // interface-preserving edit did not touch, so re-running it | |
| 1022 | // here restores its inlays against the refreshed locations | |
| 1023 | // rather than leaving them absent until the next full build. | |
| 1024 | container.definition_virtuality.apply(source_file) | |
| 1025 | ||
| 1026 | // Same reasoning: check-name-conventions is another | |
| 1027 | // whole-file, declaration-level walk that only otherwise runs | |
| 1028 | // as part of build_passes. Without this, a name-convention | |
| 1029 | // warning on a declaration an interface-preserving edit did | |
| 1030 | // not touch is dropped by the clear above and never comes | |
| 1031 | // back - `are_tables_current` staying true means the | |
| 1032 | // whole-project rebuild that would re-run it never happens | |
| 1033 | // again for this file. | |
| 1034 | container.check_name_conventions.apply(source_file) | |
| 1035 | ||
| 1036 | logger.set_is_compiling_expressions(true) | |
| 1037 | ||
| 1038 | // The re-walk drives the visitor directly rather than through | |
| 1039 | // its apply(), so the state apply() drops at a walk boundary | |
| 1040 | // has to be dropped here instead. Left in place, the phantom | |
| 1041 | // type-arg placeholders minted during the previous walk stay | |
| 1042 | // in the registry, and the first function whose retry loop | |
| 1043 | // fails to converge sweeps them and reports them against | |
| 1044 | // their pre-edit locations. | |
| 1045 | container.compile_expressions.reset_between_walks() | |
| 1046 | ||
| 1047 | // Driven over the re-walked bodies rather than through | |
| 1048 | // `apply`, so the marking is bracketed by hand. | |
| 1049 | container.mark_boxed_locals.begin_marking() | |
| 1050 | rewalk_pass(source_file, "mark-boxed-locals", container.mark_boxed_locals) | |
| 1051 | container.mark_boxed_locals.end_marking(_want_boxing) | |
| 1052 | ||
| 1053 | rewalk_pass(source_file, "compile-expressions", container.compile_expressions) | |
| 1054 | ||
| 1055 | logger.set_is_compiling_expressions(false) | |
| 1056 | ||
| 1057 | // Every constructor this re-walk touched has a fresh | |
| 1058 | // summary; without this, the field check's report() - | |
| 1059 | // called only from a whole-project build and from | |
| 1060 | // compile_expressions_only - never runs on this path, and | |
| 1061 | // a field a prior full build already flagged (or cleared) | |
| 1062 | // stops tracking the source entirely once analysis mode | |
| 1063 | // starts driving edits through incremental re-walk. | |
| 1064 | container.compile_expressions.report_field_assignments() | |
| 1065 | si | |
| 1066 | ||
| 1067 | // Run one body-visiting pass over the edited file's bodies only, | |
| 1068 | // driven by BODY_REWALKER, under a scope-stack mark/release. The | |
| 1069 | // pass name is passed separately because a body-visiting pass is | |
| 1070 | // a bare visitor, not a registered Pass with a description. | |
| 1071 | rewalk_pass(source_file: SOURCE_FILE, pass_name: string, pass: Syntax.Visitor) is | |
| 1072 | WORK_COUNTERS.file_rewalk(container.timers, pass_name) | |
| 1073 | ||
| 1074 | let symbol_table = container.symbol_table | |
| 1075 | let mark = symbol_table.mark_scope_stack() | |
| 1076 | let diagnostics_mark = logger.mark() | |
| 1077 | ||
| 1078 | let rewalker = Syntax.Process.BODY_REWALKER( | |
| 1079 | logger, | |
| 1080 | symbol_table, | |
| 1081 | container.namespaces, | |
| 1082 | pass | |
| 1083 | ) | |
| 1084 | ||
| 1085 | try | |
| 1086 | source_file.definition.walk(rewalker) | |
| 1087 | catch e: Exception | |
| 1088 | logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception in incremental re-walk of {source_file}") | |
| 1089 | finally | |
| 1090 | logger.release(diagnostics_mark) | |
| 1091 | symbol_table.release_scope_stack(mark) | |
| 1092 | container.namespaces.pop_all_namespaces() | |
| 1093 | yrt | |
| 1094 | si | |
| 1095 | ||
| 1096 | conditional_compilation_pass(source_file: SOURCE_FILE) -> bool is | |
| 1097 | let definition = source_file.definition | |
| 1098 | ||
| 1099 | container | |
| 1100 | .conditional_compilation | |
| 1101 | .apply( | |
| 1102 | definition | |
| 1103 | ) | |
| 1104 | ||
| 1105 | return true | |
| 1106 | si | |
| 1107 | ||
| 1108 | rewrite_syntax_tree_pass(source_file: SOURCE_FILE) -> bool is | |
| 1109 | let definition = source_file.definition | |
| 1110 | ||
| 1111 | container | |
| 1112 | .rewrite_primary_constructors | |
| 1113 | .apply(definition) | |
| 1114 | ||
| 1115 | container | |
| 1116 | .synthesise_top_level_entry | |
| 1117 | .apply(definition) | |
| 1118 | ||
| 1119 | container | |
| 1120 | .synthesise_entry_environment_wrapper | |
| 1121 | .apply(definition) | |
| 1122 | ||
| 1123 | container | |
| 1124 | .expand_namespaces | |
| 1125 | .apply(definition) | |
| 1126 | ||
| 1127 | container | |
| 1128 | .add_accessors_for_properties | |
| 1129 | .apply(definition) | |
| 1130 | ||
| 1131 | container | |
| 1132 | .spill_awaits | |
| 1133 | .apply(definition) | |
| 1134 | ||
| 1135 | return true | |
| 1136 | si | |
| 1137 | ||
| 1138 | collect_suppress_pragmas_pass(source_file: SOURCE_FILE) -> bool is | |
| 1139 | container | |
| 1140 | .collect_suppress_pragmas | |
| 1141 | .apply(source_file) | |
| 1142 | ||
| 1143 | return true | |
| 1144 | si | |
| 1145 | ||
| 1146 | collect_modifier_keyword_locations_pass(source_file: SOURCE_FILE) -> bool is | |
| 1147 | container | |
| 1148 | .collect_modifier_keyword_locations | |
| 1149 | .apply(source_file) | |
| 1150 | ||
| 1151 | return true | |
| 1152 | si | |
| 1153 | ||
| 1154 | check_name_conventions_pass(source_file: SOURCE_FILE) -> bool is | |
| 1155 | container | |
| 1156 | .check_name_conventions | |
| 1157 | .apply(source_file) | |
| 1158 | ||
| 1159 | return true | |
| 1160 | si | |
| 1161 | ||
| 1162 | // Settle the entry point outside a build. The debounced COMPILE | |
| 1163 | // an editor sends once edits stop is the one place the answer is | |
| 1164 | // wanted in analysis mode, and by then its own path may have | |
| 1165 | // rebuilt nothing - the edit that preceded it already did. | |
| 1166 | select_entry_point(files: Collections.Iterable[SOURCE_FILE]) is | |
| 1167 | container.select_entry_point.start() | |
| 1168 | ||
| 1169 | for file in files do | |
| 1170 | container.select_entry_point.apply(file) | |
| 1171 | od | |
| 1172 | ||
| 1173 | container.select_entry_point.finish() | |
| 1174 | si | |
| 1175 | ||
| 1176 | select_entry_point_pass(source_file: SOURCE_FILE) -> bool is | |
| 1177 | if !_want_entry_point_selection then | |
| 1178 | return false | |
| 1179 | fi | |
| 1180 | ||
| 1181 | container | |
| 1182 | .select_entry_point | |
| 1183 | .apply(source_file) | |
| 1184 | ||
| 1185 | return true | |
| 1186 | si | |
| 1187 | ||
| 1188 | definition_virtuality_pass(source_file: SOURCE_FILE) -> bool is | |
| 1189 | container | |
| 1190 | .definition_virtuality | |
| 1191 | .apply(source_file) | |
| 1192 | ||
| 1193 | return true | |
| 1194 | si | |
| 1195 | ||
| 1196 | declare_symbols_pass(source_file: SOURCE_FILE) -> bool is | |
| 1197 | let flags = container.build_flags | |
| 1198 | let definition = source_file.definition | |
| 1199 | ||
| 1200 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1201 | container | |
| 1202 | .declare_symbols | |
| 1203 | .apply(definition) | |
| 1204 | ||
| 1205 | return true | |
| 1206 | fi | |
| 1207 | ||
| 1208 | return false | |
| 1209 | si | |
| 1210 | ||
| 1211 | resolve_uses_pass(source_file: SOURCE_FILE) -> bool is | |
| 1212 | let flags = container.build_flags | |
| 1213 | let definition = source_file.definition | |
| 1214 | ||
| 1215 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1216 | container | |
| 1217 | .resolve_uses | |
| 1218 | .apply(definition) | |
| 1219 | ||
| 1220 | return true | |
| 1221 | fi | |
| 1222 | ||
| 1223 | return false | |
| 1224 | si | |
| 1225 | ||
| 1226 | declare_members_pass(source_file: SOURCE_FILE) -> bool is | |
| 1227 | let flags = container.build_flags | |
| 1228 | let definition = source_file.definition | |
| 1229 | ||
| 1230 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1231 | container | |
| 1232 | .declare_members | |
| 1233 | .apply(definition) | |
| 1234 | ||
| 1235 | return true | |
| 1236 | fi | |
| 1237 | ||
| 1238 | return false | |
| 1239 | si | |
| 1240 | ||
| 1241 | resolve_member_uses_pass(source_file: SOURCE_FILE) -> bool is | |
| 1242 | let flags = container.build_flags | |
| 1243 | let definition = source_file.definition | |
| 1244 | ||
| 1245 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1246 | container | |
| 1247 | .resolve_member_uses | |
| 1248 | .apply(definition) | |
| 1249 | ||
| 1250 | return true | |
| 1251 | fi | |
| 1252 | ||
| 1253 | return false | |
| 1254 | si | |
| 1255 | ||
| 1256 | resolve_ancestors_pass(source_file: SOURCE_FILE) -> bool is | |
| 1257 | let flags = container.build_flags | |
| 1258 | let definition = source_file.definition | |
| 1259 | ||
| 1260 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1261 | container | |
| 1262 | .resolve_ancestors | |
| 1263 | .apply(definition) | |
| 1264 | ||
| 1265 | return true | |
| 1266 | fi | |
| 1267 | ||
| 1268 | return false | |
| 1269 | si | |
| 1270 | ||
| 1271 | synthesize_class_equality_pass(source_file: SOURCE_FILE) -> bool is | |
| 1272 | let flags = container.build_flags | |
| 1273 | let definition = source_file.definition | |
| 1274 | ||
| 1275 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1276 | container | |
| 1277 | .synthesize_class_equality | |
| 1278 | .apply(definition) | |
| 1279 | ||
| 1280 | return true | |
| 1281 | fi | |
| 1282 | ||
| 1283 | return false | |
| 1284 | si | |
| 1285 | ||
| 1286 | synthesize_iterator_reset_pass(source_file: SOURCE_FILE) -> bool is | |
| 1287 | let flags = container.build_flags | |
| 1288 | let definition = source_file.definition | |
| 1289 | ||
| 1290 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1291 | container | |
| 1292 | .synthesize_iterator_reset | |
| 1293 | .apply(definition) | |
| 1294 | ||
| 1295 | return true | |
| 1296 | fi | |
| 1297 | ||
| 1298 | return false | |
| 1299 | si | |
| 1300 | ||
| 1301 | resolve_type_expressions_pass(source_file: SOURCE_FILE) -> bool is | |
| 1302 | let flags = container.build_flags | |
| 1303 | let definition = source_file.definition | |
| 1304 | ||
| 1305 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1306 | container | |
| 1307 | .resolve_type_expressions | |
| 1308 | .apply(definition) | |
| 1309 | ||
| 1310 | return true | |
| 1311 | fi | |
| 1312 | ||
| 1313 | return false | |
| 1314 | si | |
| 1315 | ||
| 1316 | resolve_explicit_types_pass(source_file: SOURCE_FILE) -> bool is | |
| 1317 | let flags = container.build_flags | |
| 1318 | let definition = source_file.definition | |
| 1319 | ||
| 1320 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1321 | container | |
| 1322 | .resolve_explicit_types | |
| 1323 | .apply(definition) | |
| 1324 | ||
| 1325 | return true | |
| 1326 | fi | |
| 1327 | ||
| 1328 | return false | |
| 1329 | si | |
| 1330 | ||
| 1331 | check_type_argument_bounds_pass(source_file: SOURCE_FILE) -> bool is | |
| 1332 | let flags = container.build_flags | |
| 1333 | let definition = source_file.definition | |
| 1334 | ||
| 1335 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1336 | container | |
| 1337 | .check_type_argument_bounds | |
| 1338 | .apply(definition) | |
| 1339 | ||
| 1340 | return true | |
| 1341 | fi | |
| 1342 | ||
| 1343 | return false | |
| 1344 | si | |
| 1345 | ||
| 1346 | verify_compile_state_pass(source_file: SOURCE_FILE) -> bool is | |
| 1347 | if source_file.want_compile_expressions /\ !_verify_compile_state_had_errors then | |
| 1348 | container | |
| 1349 | .verify_compile_state | |
| 1350 | .apply(source_file.definition) | |
| 1351 | ||
| 1352 | return true | |
| 1353 | fi | |
| 1354 | ||
| 1355 | return false | |
| 1356 | si | |
| 1357 | ||
| 1358 | compile_expressions_pass(source_file: SOURCE_FILE) -> bool is | |
| 1359 | let definition = source_file.definition | |
| 1360 | ||
| 1361 | if source_file.want_compile_expressions then | |
| 1362 | container | |
| 1363 | .compile_expressions | |
| 1364 | .apply(definition) | |
| 1365 | ||
| 1366 | return true | |
| 1367 | fi | |
| 1368 | ||
| 1369 | return false | |
| 1370 | si | |
| 1371 | ||
| 1372 | // Whether the boxing flags are worth deriving from the | |
| 1373 | // marking. Boxing is a code generation strategy; the marking | |
| 1374 | // itself is needed on every build, because flow narrowing | |
| 1375 | // consults it. | |
| 1376 | _want_boxing: bool => | |
| 1377 | container.build_flags.want_assembler \/ | |
| 1378 | container.build_flags.want_executable | |
| 1379 | ||
| 1380 | mark_boxed_locals_pass(source_file: SOURCE_FILE) -> bool is | |
| 1381 | let definition = source_file.definition | |
| 1382 | ||
| 1383 | // The capture and reassignment marking is needed on | |
| 1384 | // every build, because flow narrowing consults it to | |
| 1385 | // decline facts on a local a closure body can rewrite. | |
| 1386 | // Boxing only matters once the IR is lowered to IL, so | |
| 1387 | // deriving `is_boxed` from the same flags stays gated on | |
| 1388 | // an IL-bound build. Either way, pair the gate with | |
| 1389 | // compile-expressions - if the file isn't being walked | |
| 1390 | // for expression-level semantics this run, no downstream | |
| 1391 | // consumer would see the flags we'd set. | |
| 1392 | if source_file.want_compile_expressions then | |
| 1393 | container | |
| 1394 | .mark_boxed_locals | |
| 1395 | .apply(definition, _want_boxing) | |
| 1396 | ||
| 1397 | return true | |
| 1398 | fi | |
| 1399 | ||
| 1400 | return false | |
| 1401 | si | |
| 1402 | ||
| 1403 | resolve_overrides_pass(source_file: SOURCE_FILE) -> bool is | |
| 1404 | let definition = source_file.definition | |
| 1405 | ||
| 1406 | if source_file.want_compile_up_to_expressions then | |
| 1407 | container | |
| 1408 | .resolve_overrides | |
| 1409 | .apply(definition) | |
| 1410 | ||
| 1411 | return true | |
| 1412 | fi | |
| 1413 | ||
| 1414 | return false | |
| 1415 | si | |
| 1416 | ||
| 1417 | // The re-walk loop of the combined type + effect solve | |
| 1418 | // prototype. A body walked before any relations were solved, | |
| 1419 | // or against relations an edit has since changed, may disagree | |
| 1420 | // with the relations just solved: the judge reports facts it | |
| 1421 | // kept that are unbacked, and the ledger reports facts it | |
| 1422 | // dropped that are backed. Files with either are re-walked | |
| 1423 | // against the new relations, the relations are solved again, | |
| 1424 | // and the loop stops when a solve finds every file exact. | |
| 1425 | // Widening can only add callees and writes, and a re-walk | |
| 1426 | // only reads the relations, so the loop is bounded; the cap is | |
| 1427 | // a guard. See KILL_LEDGER. | |
| 1428 | run_combined_solve_rounds(files: Collections.Iterable[SOURCE_FILE]) is | |
| 1429 | if !Syntax.Process.EFFECTS.is_complete then | |
| 1430 | return | |
| 1431 | fi | |
| 1432 | ||
| 1433 | let by_name = Collections.MAP[string, SOURCE_FILE]() | |
| 1434 | ||
| 1435 | for source_file in files do | |
| 1436 | by_name[source_file.file_name] = source_file | |
| 1437 | od | |
| 1438 | ||
| 1439 | let round mut = 1 | |
| 1440 | let max_rounds = 16 | |
| 1441 | ||
| 1442 | while round <= max_rounds do | |
| 1443 | let inexact = Syntax.Process.KILL_LEDGER.take_inexact() | |
| 1444 | ||
| 1445 | if inexact.is_empty then | |
| 1446 | break | |
| 1447 | fi | |
| 1448 | ||
| 1449 | // Whole-file re-walks, for comparing against the per-body form. | |
| 1450 | if System.Environment.get_environment_variable("GHUL_SOLVE_FILEWISE")? then | |
| 1451 | for function in inexact.bodies do | |
| 1452 | inexact.files.add(function.location.file_name) | |
| 1453 | od | |
| 1454 | ||
| 1455 | inexact.bodies.clear() | |
| 1456 | fi | |
| 1457 | ||
| 1458 | let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? | |
| 1459 | let sw = System.Diagnostics.Stopwatch.start_new() | |
| 1460 | ||
| 1461 | Syntax.Process.KILL_LEDGER.in_descent = true | |
| 1462 | ||
| 1463 | let rewalked = Collections.SET[SOURCE_FILE]() | |
| 1464 | ||
| 1465 | // A file re-walked whole covers its inexact bodies too. | |
| 1466 | for file_name in inexact.files do | |
| 1467 | let source_file: SOURCE_FILE mut | |
| 1468 | ||
| 1469 | if by_name.try_get_value(file_name, source_file ref) then | |
| 1470 | recompile_file_expressions(source_file) | |
| 1471 | rewalked.add(source_file) | |
| 1472 | fi | |
| 1473 | od | |
| 1474 | ||
| 1475 | for function in inexact.bodies do | |
| 1476 | let source_file: SOURCE_FILE mut | |
| 1477 | ||
| 1478 | if by_name.try_get_value(function.location.file_name, source_file ref) then | |
| 1479 | if !inexact.files.contains(source_file.file_name) then | |
| 1480 | recompile_body_expressions(source_file, function) | |
| 1481 | fi | |
| 1482 | ||
| 1483 | rewalked.add(source_file) | |
| 1484 | fi | |
| 1485 | od | |
| 1486 | ||
| 1487 | Syntax.Process.KILL_LEDGER.in_descent = false | |
| 1488 | ||
| 1489 | let recompile_ms = sw.elapsed_milliseconds | |
| 1490 | ||
| 1491 | let names = Collections.LIST[string]() | |
| 1492 | ||
| 1493 | for source_file in rewalked do | |
| 1494 | names.add(source_file.file_name) | |
| 1495 | od | |
| 1496 | ||
| 1497 | _run_partial_effects_round(files, names) | |
| 1498 | ||
| 1499 | if timing then | |
| 1500 | IO.Std.error.write_line("combined solve round {round}: recompile {recompile_ms} ms, effects {sw.elapsed_milliseconds - recompile_ms} ms") | |
| 1501 | fi | |
| 1502 | ||
| 1503 | round = round + 1 | |
| 1504 | od | |
| 1505 | si | |
| 1506 | ||
| 1507 | _dump_kills() is | |
| 1508 | if let dump = System.Environment.get_environment_variable("GHUL_KILL_DUMP") then | |
| 1509 | let writer = IO.File.append_text(dump) | |
| 1510 | writer.write_line("=== solve") | |
| 1511 | Syntax.Process.KILL_LEDGER.dump(writer) | |
| 1512 | writer.dispose() | |
| 1513 | fi | |
| 1514 | si | |
| 1515 | ||
| 1516 | // The infer-effects pass as a directly callable round, for the | |
| 1517 | // analysis-mode COMPILE paths that recompile expressions | |
| 1518 | // without running the pass list: re-walk every file's bodies | |
| 1519 | // with resolved types, solve the effect relations, and judge | |
| 1520 | // whatever reliances those recompiles recorded. Without this, | |
| 1521 | // a reliance recorded on an incremental path would never be | |
| 1522 | // reported. | |
| 1523 | run_effects_pass(files: Collections.Iterable[SOURCE_FILE]) is | |
| 1524 | let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? | |
| 1525 | let sw = System.Diagnostics.Stopwatch.start_new() | |
| 1526 | ||
| 1527 | _restart_descent(files) | |
| 1528 | ||
| 1529 | let t_restart = sw.elapsed_milliseconds | |
| 1530 | ||
| 1531 | // Only the files walked since the last solve can have | |
| 1532 | // changed what it reads; everything else's facts are | |
| 1533 | // retained. That needs a complete solve to build on, and | |
| 1534 | // for every such file to have been compiled through | |
| 1535 | // expressions, or the round is the full one. | |
| 1536 | let stale = Collections.LIST[string]() | |
| 1537 | ||
| 1538 | for file_name in _effects_stale_files do | |
| 1539 | stale.add(file_name) | |
| 1540 | od | |
| 1541 | ||
| 1542 | if _can_solve_partially(files, stale) then | |
| 1543 | _run_partial_effects_round(files, stale) | |
| 1544 | else | |
| 1545 | _run_effects_round(files) | |
| 1546 | fi | |
| 1547 | ||
| 1548 | let t_round = sw.elapsed_milliseconds | |
| 1549 | ||
| 1550 | run_combined_solve_rounds(files) | |
| 1551 | ||
| 1552 | let t_rounds = sw.elapsed_milliseconds | |
| 1553 | ||
| 1554 | _dump_kills() | |
| 1555 | ||
| 1556 | run_post_effects_purity_checks(files) | |
| 1557 | ||
| 1558 | if timing then | |
| 1559 | IO.Std.error.write_line("effects pass: restart {t_restart} round {t_round - t_restart} descent {t_rounds - t_round} purity {sw.elapsed_milliseconds - t_rounds} ms") | |
| 1560 | fi | |
| 1561 | si | |
| 1562 | ||
| 1563 | // The answer is defined as the descent from every fact kept, which | |
| 1564 | // is what a batch build does. An edit path walks a body against | |
| 1565 | // the last complete solve instead, which is fast but starts the | |
| 1566 | // descent lower than the top, and from there it can settle short | |
| 1567 | // of the batch answer. So a COMPILE after any such walk first | |
| 1568 | // re-walks the bodies that killed outside a descent with kills | |
| 1569 | // suspended; every other body walked as it would have from the | |
| 1570 | // top already. | |
| 1571 | _restart_descent(files: Collections.Iterable[SOURCE_FILE]) is | |
| 1572 | Syntax.Process.KILL_LEDGER.kills_suspended = false | |
| 1573 | ||
| 1574 | if !Syntax.Process.KILL_LEDGER.needs_restart then | |
| 1575 | return | |
| 1576 | fi | |
| 1577 | ||
| 1578 | let by_name = Collections.MAP[string, SOURCE_FILE]() | |
| 1579 | ||
| 1580 | for source_file in files do | |
| 1581 | by_name[source_file.file_name] = source_file | |
| 1582 | od | |
| 1583 | ||
| 1584 | let bodies = Syntax.Process.KILL_LEDGER.take_restart_bodies() | |
| 1585 | let sw = System.Diagnostics.Stopwatch.start_new() | |
| 1586 | ||
| 1587 | Syntax.Process.KILL_LEDGER.kills_suspended = true | |
| 1588 | ||
| 1589 | for function in bodies do | |
| 1590 | let source_file: SOURCE_FILE mut | |
| 1591 | ||
| 1592 | if by_name.try_get_value(function.location.file_name, source_file ref) then | |
| 1593 | recompile_body_expressions(source_file, function) | |
| 1594 | fi | |
| 1595 | od | |
| 1596 | ||
| 1597 | Syntax.Process.KILL_LEDGER.kills_suspended = false | |
| 1598 | Syntax.Process.KILL_LEDGER.needs_restart = false | |
| 1599 | ||
| 1600 | if System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? then | |
| 1601 | IO.Std.error.write_line("descent restarted from the top: {bodies.count} body(ies) re-walked in {sw.elapsed_milliseconds} ms") | |
| 1602 | fi | |
| 1603 | si | |
| 1604 | ||
| 1605 | // One walk-and-solve round of the effects pass. The judge and | |
| 1606 | // the purity checks run once, against the relations the last | |
| 1607 | // round solved. | |
| 1608 | _run_effects_round(files: Collections.Iterable[SOURCE_FILE]) is | |
| 1609 | _effects_stale_files.clear() | |
| 1610 | ||
| 1611 | Syntax.Process.EFFECT_FACTS.collecting = true | |
| 1612 | Syntax.Process.EFFECT_FACTS.begin_generation("infer-effects") | |
| 1613 | ||
| 1614 | container.infer_store_free.set_resolved_mode(true) | |
| 1615 | container.infer_store_free.start_run() | |
| 1616 | ||
| 1617 | for source_file in files do | |
| 1618 | infer_effects_pass(source_file) | |
| 1619 | od | |
| 1620 | ||
| 1621 | container.infer_store_free.solve_effects(_all_compiled_through_expressions(files)) | |
| 1622 | container.infer_store_free.set_resolved_mode(false) | |
| 1623 | ||
| 1624 | Syntax.Process.EFFECT_FACTS.collecting = false | |
| 1625 | si | |
| 1626 | ||
| 1627 | // Whether every one of these files has had its expressions | |
| 1628 | // compiled, which is what makes a solve over them complete. | |
| 1629 | _all_compiled_through_expressions(files: Collections.Iterable[SOURCE_FILE]) -> bool is | |
| 1630 | let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? | |
| 1631 | let complete mut = true | |
| 1632 | ||
| 1633 | for source_file in files do | |
| 1634 | if !is_compiled_through_expressions(source_file) then | |
| 1635 | if timing then | |
| 1636 | IO.Std.error.write_line("effects: solve incomplete, not compiled through expressions: {source_file.file_name} (compiled_through {source_file.compiled_through?.description ?? "none"})") | |
| 1637 | fi | |
| 1638 | ||
| 1639 | complete = false | |
| 1640 | fi | |
| 1641 | od | |
| 1642 | ||
| 1643 | return complete | |
| 1644 | si | |
| 1645 | ||
| 1646 | // Whether the stale files can be solved as a partial round over | |
| 1647 | // the retained facts: the relations in use were solved from | |
| 1648 | // them, and every stale file still present is compiled through | |
| 1649 | // expressions. | |
| 1650 | _can_solve_partially(files: Collections.Iterable[SOURCE_FILE], stale: Collections.List[string]) -> bool is | |
| 1651 | if !container.infer_store_free.solved_over_facts then | |
| 1652 | return false | |
| 1653 | fi | |
| 1654 | ||
| 1655 | let names = Collections.SET[string]() | |
| 1656 | ||
| 1657 | for file_name in stale do | |
| 1658 | names.add(file_name) | |
| 1659 | od | |
| 1660 | ||
| 1661 | for source_file in files do | |
| 1662 | if names.contains(source_file.file_name) /\ !is_compiled_through_expressions(source_file) then | |
| 1663 | return false | |
| 1664 | fi | |
| 1665 | od | |
| 1666 | ||
| 1667 | return true | |
| 1668 | si | |
| 1669 | ||
| 1670 | // A round over only the files whose bodies were re-walked: their | |
| 1671 | // facts are dropped and re-derived, every other file's are kept | |
| 1672 | // from the previous round, and the relations are solved over | |
| 1673 | // the union. The previous round left the facts in place for | |
| 1674 | // exactly this. | |
| 1675 | _run_partial_effects_round(files: Collections.Iterable[SOURCE_FILE], names: Collections.List[string]) is | |
| 1676 | _effects_stale_files.clear() | |
| 1677 | ||
| 1678 | Syntax.Process.EFFECT_FACTS.collecting = true | |
| 1679 | ||
| 1680 | container.infer_store_free.set_resolved_mode(true) | |
| 1681 | ||
| 1682 | let by_name = Collections.MAP[string, SOURCE_FILE]() | |
| 1683 | ||
| 1684 | for source_file in files do | |
| 1685 | by_name[source_file.file_name] = source_file | |
| 1686 | od | |
| 1687 | ||
| 1688 | // The re-walk usually re-derives exactly what it replaces: a | |
| 1689 | // kill widens a receiver, and the widened dispatch reaches the | |
| 1690 | // same callees and writes the same members; an edit that | |
| 1691 | // touches no call or store leaves them alone too. Then the | |
| 1692 | // installed relations already answer for the facts and the | |
| 1693 | // solve is skipped. | |
| 1694 | let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? | |
| 1695 | let sw = System.Diagnostics.Stopwatch.start_new() | |
| 1696 | ||
| 1697 | let snapshot = container.infer_store_free.snapshot_inputs(names) | |
| 1698 | ||
| 1699 | for file_name in names do | |
| 1700 | container.infer_store_free.drop_file(file_name) | |
| 1701 | od | |
| 1702 | ||
| 1703 | for file_name in names do | |
| 1704 | let source_file: SOURCE_FILE mut | |
| 1705 | ||
| 1706 | if by_name.try_get_value(file_name, source_file ref) then | |
| 1707 | infer_effects_pass(source_file) | |
| 1708 | fi | |
| 1709 | od | |
| 1710 | ||
| 1711 | if timing then | |
| 1712 | IO.Std.error.write_line("effects: partial round snapshot, drop and re-walk {sw.elapsed_milliseconds} ms") | |
| 1713 | fi | |
| 1714 | ||
| 1715 | let changed = container.infer_store_free.changed_inputs(names, snapshot) | |
| 1716 | ||
| 1717 | if changed.count == 0 then | |
| 1718 | if timing then | |
| 1719 | IO.Std.error.write_line("effects: partial round over {names.count} file(s) changed no solve input, relations kept") | |
| 1720 | fi | |
| 1721 | else | |
| 1722 | if timing then | |
| 1723 | IO.Std.error.write_line("effects: partial round over {names.count} file(s) changed the inputs of {changed.count} function(s), re-solving") | |
| 1724 | fi | |
| 1725 | ||
| 1726 | container.infer_store_free.resolve_effects_for(changed) | |
| 1727 | fi | |
| 1728 | ||
| 1729 | container.infer_store_free.set_resolved_mode(false) | |
| 1730 | ||
| 1731 | Syntax.Process.EFFECT_FACTS.collecting = false | |
| 1732 | si | |
| 1733 | ||
| 1734 | // The two purity checks that need a body's own store-freedom: | |
| 1735 | // what reached a pure function-typed slot, and whether an | |
| 1736 | // operator or override that has to be pure actually is. Both | |
| 1737 | // ask the solved effect relations, so both run here rather | |
| 1738 | // than against the declared-types-only approximation that is | |
| 1739 | // all a pass before compile-expressions could have. | |
| 1740 | run_post_effects_purity_checks(files: Collections.Iterable[SOURCE_FILE]) is | |
| 1741 | logger.set_is_compiling_expressions(true) | |
| 1742 | ||
| 1743 | Syntax.Process.PURE_SLOT_JUDGE( | |
| 1744 | container.logger, | |
| 1745 | container.infer_store_free.function_values).judge() | |
| 1746 | ||
| 1747 | for source_file in files do | |
| 1748 | if source_file.want_compile_up_to_expressions then | |
| 1749 | check_pure_overrides_pass(source_file) | |
| 1750 | fi | |
| 1751 | od | |
| 1752 | ||
| 1753 | logger.set_is_compiling_expressions(false) | |
| 1754 | si | |
| 1755 | ||
| 1756 | infer_effects_pass(source_file: SOURCE_FILE) -> bool is | |
| 1757 | let definition = source_file.definition | |
| 1758 | ||
| 1759 | if source_file.want_compile_up_to_expressions then | |
| 1760 | // Always the full body re-walk: the per-file facts | |
| 1761 | // retained for the pre-expressions run were derived | |
| 1762 | // from declared types only, and reusing them here would | |
| 1763 | // reintroduce exactly the imprecision this pass exists | |
| 1764 | // to remove. | |
| 1765 | container.infer_store_free.apply(definition) | |
| 1766 | ||
| 1767 | return true | |
| 1768 | fi | |
| 1769 | ||
| 1770 | return false | |
| 1771 | si | |
| 1772 | ||
| 1773 | register_source_intrinsics_pass(source_file: SOURCE_FILE) -> bool is | |
| 1774 | let flags = container.build_flags | |
| 1775 | ||
| 1776 | if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then | |
| 1777 | container | |
| 1778 | .register_source_intrinsics | |
| 1779 | .apply(source_file.definition) | |
| 1780 | ||
| 1781 | return true | |
| 1782 | fi | |
| 1783 | ||
| 1784 | return false | |
| 1785 | si | |
| 1786 | ||
| 1787 | check_pure_overrides_pass(source_file: SOURCE_FILE) -> bool is | |
| 1788 | let definition = source_file.definition | |
| 1789 | ||
| 1790 | if source_file.want_compile_up_to_expressions then | |
| 1791 | container | |
| 1792 | .check_pure_overrides | |
| 1793 | .apply(definition) | |
| 1794 | ||
| 1795 | return true | |
| 1796 | fi | |
| 1797 | ||
| 1798 | return false | |
| 1799 | si | |
| 1800 | ||
| 1801 | record_type_argument_uses_pass(source_file: SOURCE_FILE) -> bool is | |
| 1802 | let flags = container.build_flags | |
| 1803 | ||
| 1804 | if flags.want_assembler \/ flags.want_executable then | |
| 1805 | let definition = source_file.definition | |
| 1806 | ||
| 1807 | container | |
| 1808 | .record_type_argument_uses | |
| 1809 | .apply(definition) | |
| 1810 | ||
| 1811 | return true | |
| 1812 | fi | |
| 1813 | ||
| 1814 | return false | |
| 1815 | si | |
| 1816 | ||
| 1817 | // Whether this build produces an assembly. Analysis mode does not, | |
| 1818 | // and the generate-il pass then has nothing to do - including in | |
| 1819 | // its whole-build hooks, which lay out and write the metadata for | |
| 1820 | // an image nobody asks for. | |
| 1821 | is_emitting_assembly: bool => | |
| 1822 | let flags = container.build_flags in | |
| 1823 | flags.want_assembler \/ flags.want_executable | |
| 1824 | ||
| 1825 | generate_il_pass(source_file: SOURCE_FILE) -> bool is | |
| 1826 | if is_emitting_assembly then | |
| 1827 | let definition = source_file.definition | |
| 1828 | ||
| 1829 | container | |
| 1830 | .generate_il | |
| 1831 | .apply(definition) | |
| 1832 | ||
| 1833 | return true | |
| 1834 | fi | |
| 1835 | ||
| 1836 | return false | |
| 1837 | si | |
| 1838 | si | |
| 1839 | si |