Appearance
| 1 | namespace Logging is | |
| 2 | use System.Exception; | |
| 3 | ||
| 4 | use Collections.Map; | |
| 5 | use Collections.MutableMap; | |
| 6 | use Collections.List; | |
| 7 | use Collections.MutableList; | |
| 8 | use Collections.Iterable; | |
| 9 | ||
| 10 | use Collections.MAP; | |
| 11 | use Collections.LIST; | |
| 12 | use Collections.STACK; | |
| 13 | ||
| 14 | use IO.TextWriter; | |
| 15 | ||
| 16 | use Ghul.Pipes; | |
| 17 | ||
| 18 | use Source.LOCATION; | |
| 19 | ||
| 20 | struct DIAGNOSTIC_MESSAGE is | |
| 21 | is_analysis: bool; | |
| 22 | is_compile_expressions: bool; | |
| 23 | severity: DiagnosticSeverity; | |
| 24 | location: LOCATION; | |
| 25 | code: string?; | |
| 26 | text: string; | |
| 27 | // Secondary text for an INLAY carrier. For a narrowing-introduction | |
| 28 | // carrier this holds only the narrowed-to type; the hover sentence | |
| 29 | // is assembled at read-out. For a kill carrier it holds the full | |
| 30 | // hover text. Null for every other diagnostic. | |
| 31 | detail: string?; | |
| 32 | is_hint: bool => (severity == DiagnosticSeverity.HINT); | |
| 33 | is_inlay: bool => (severity == DiagnosticSeverity.INLAY); | |
| 34 | is_fatal: bool => (severity == DiagnosticSeverity.FATAL \/ severity == DiagnosticSeverity.EXCEPTION); | |
| 35 | ||
| 36 | init( | |
| 37 | is_analysis: bool, | |
| 38 | is_compile_expressions: bool, | |
| 39 | severity: DiagnosticSeverity, | |
| 40 | location: LOCATION, | |
| 41 | text: string | |
| 42 | ) is | |
| 43 | self.is_analysis = is_analysis; | |
| 44 | self.is_compile_expressions = is_compile_expressions; | |
| 45 | self.severity = severity; | |
| 46 | self.location = location; | |
| 47 | self.code = null; | |
| 48 | self.text = text; | |
| 49 | self.detail = null; | |
| 50 | si | |
| 51 | ||
| 52 | init( | |
| 53 | is_analysis: bool, | |
| 54 | is_compile_expressions: bool, | |
| 55 | severity: DiagnosticSeverity, | |
| 56 | location: LOCATION, | |
| 57 | code: string?, | |
| 58 | text: string | |
| 59 | ) is | |
| 60 | self.is_analysis = is_analysis; | |
| 61 | self.is_compile_expressions = is_compile_expressions; | |
| 62 | self.severity = severity; | |
| 63 | self.location = location; | |
| 64 | self.code = code; | |
| 65 | self.text = text; | |
| 66 | self.detail = null; | |
| 67 | si | |
| 68 | ||
| 69 | init( | |
| 70 | is_analysis: bool, | |
| 71 | is_compile_expressions: bool, | |
| 72 | severity: DiagnosticSeverity, | |
| 73 | location: LOCATION, | |
| 74 | code: string?, | |
| 75 | text: string, | |
| 76 | detail: string? | |
| 77 | ) is | |
| 78 | self.is_analysis = is_analysis; | |
| 79 | self.is_compile_expressions = is_compile_expressions; | |
| 80 | self.severity = severity; | |
| 81 | self.location = location; | |
| 82 | self.code = code; | |
| 83 | self.text = text; | |
| 84 | self.detail = detail; | |
| 85 | si | |
| 86 | ||
| 87 | to_string() -> string => "{severity} {location} {text}"; | |
| 88 | si | |
| 89 | ||
| 90 | trait DiagnosticFormatter is | |
| 91 | want_filter_error_cascades: bool => false; | |
| 92 | need_clear_errors: bool => false; | |
| 93 | format(diagnostic: DIAGNOSTIC_MESSAGE) -> string?; | |
| 94 | si | |
| 95 | ||
| 96 | class TAB_DELIMITED_DIAGNOSTIC_FORMATTER: DiagnosticFormatter is | |
| 97 | want_filter_error_cascades: bool => true; | |
| 98 | need_clear_errors: bool => true; | |
| 99 | ||
| 100 | init() is | |
| 101 | si | |
| 102 | ||
| 103 | format(diagnostic: DIAGNOSTIC_MESSAGE) -> string? is | |
| 104 | // Inlay carriers are never published as diagnostics; they reach | |
| 105 | // the client only through the INLAY_HINTS query. | |
| 106 | if diagnostic.is_inlay then | |
| 107 | return null; | |
| 108 | fi | |
| 109 | ||
| 110 | let location = diagnostic.location; | |
| 111 | let severity = | |
| 112 | if diagnostic.is_fatal then | |
| 113 | cast int(DiagnosticSeverity.HINT); | |
| 114 | else | |
| 115 | cast int(diagnostic.severity); | |
| 116 | fi; | |
| 117 | ||
| 118 | let message = diagnostic.text; | |
| 119 | let code = if diagnostic.code? then diagnostic.code; else ""; fi; | |
| 120 | ||
| 121 | let s = string.format( | |
| 122 | "{{0}}\t{{1}}\t{{2}}\t{{3}}\t{{4}}\t{{5}}\t{{6}}\t{{7}}", | |
| 123 | [ | |
| 124 | location.file_name, | |
| 125 | location.start_line, | |
| 126 | location.start_column, | |
| 127 | location.end_line, | |
| 128 | location.end_column+1, | |
| 129 | cast int(severity), | |
| 130 | message, | |
| 131 | code | |
| 132 | ] | |
| 133 | ).replace('\n', ' '); | |
| 134 | ||
| 135 | return s; | |
| 136 | si | |
| 137 | si | |
| 138 | ||
| 139 | class HUMAN_READABLE_DIAGNOSTIC_FORMATTER: DiagnosticFormatter is | |
| 140 | init() is | |
| 141 | si | |
| 142 | ||
| 143 | format(diagnostic: DIAGNOSTIC_MESSAGE) -> string? is | |
| 144 | if diagnostic.is_hint then | |
| 145 | return null; | |
| 146 | fi | |
| 147 | ||
| 148 | let location = diagnostic.location; | |
| 149 | let severity = diagnostic.severity.to_string().to_lower(); | |
| 150 | let message = | |
| 151 | if diagnostic.code? then | |
| 152 | "[{diagnostic.code}] {diagnostic.text}"; | |
| 153 | else | |
| 154 | diagnostic.text; | |
| 155 | fi; | |
| 156 | ||
| 157 | let s = string.format( | |
| 158 | "{{0}}: {{1}},{{2}}..{{3}},{{4}}: {{5}}: {{6}}", | |
| 159 | [ | |
| 160 | location.file_name, | |
| 161 | location.start_line, | |
| 162 | location.start_column, | |
| 163 | location.end_line, | |
| 164 | location.end_column+1, | |
| 165 | severity, | |
| 166 | message | |
| 167 | ] | |
| 168 | ); | |
| 169 | ||
| 170 | return s; | |
| 171 | si | |
| 172 | si | |
| 173 | ||
| 174 | class MSBUILD_DIAGNOSTIC_FORMATTER: DiagnosticFormatter is | |
| 175 | init() is | |
| 176 | si | |
| 177 | ||
| 178 | format(diagnostic: Logging.DIAGNOSTIC_MESSAGE) -> string is | |
| 179 | let location = diagnostic.location; | |
| 180 | let severity = diagnostic.severity; | |
| 181 | let message = diagnostic.text; | |
| 182 | ||
| 183 | let s = string.format( | |
| 184 | "{{0}}({{1}},{{2}}): {{3}}: {{4}}", | |
| 185 | [ | |
| 186 | location.file_name, | |
| 187 | location.start_line, | |
| 188 | location.start_column, | |
| 189 | severity, | |
| 190 | message | |
| 191 | ] | |
| 192 | ); | |
| 193 | ||
| 194 | return s; | |
| 195 | si | |
| 196 | si | |
| 197 | ||
| 198 | class DIAGNOSTICS_LIST is | |
| 199 | _path: string; | |
| 200 | _dirty: bool; | |
| 201 | _diagnostics: LIST[DIAGNOSTIC_MESSAGE]; | |
| 202 | ||
| 203 | is_poisoned: bool; | |
| 204 | has_consumed_any: bool; | |
| 205 | has_consumed_error: bool; | |
| 206 | ||
| 207 | any_errors: bool => _diagnostics |> any(d => d.severity == DiagnosticSeverity.ERROR); | |
| 208 | any_warnings: bool => _diagnostics |> any(d => d.severity == DiagnosticSeverity.WARN); | |
| 209 | ||
| 210 | count: int => _diagnostics.count; | |
| 211 | error_count: int => _diagnostics |> filter(d => d.severity == DiagnosticSeverity.ERROR) |> count(); | |
| 212 | syntax_error_count: int => _diagnostics |> filter(d => !d.is_analysis /\ d.severity == DiagnosticSeverity.ERROR) |> count(); | |
| 213 | analysis_count: int => _diagnostics |> filter(d => d.is_analysis) |> count(); | |
| 214 | ||
| 215 | diagnostics: List[DIAGNOSTIC_MESSAGE] => _diagnostics; | |
| 216 | ||
| 217 | init(path: string) is | |
| 218 | _path = path; | |
| 219 | _diagnostics = LIST(); | |
| 220 | si | |
| 221 | ||
| 222 | add(diagnostic_message: DIAGNOSTIC_MESSAGE) is | |
| 223 | _dirty = true; | |
| 224 | ||
| 225 | if diagnostic_message.severity == DiagnosticSeverity.FATAL \/ diagnostic_message.severity == DiagnosticSeverity.EXCEPTION then | |
| 226 | is_poisoned = true; | |
| 227 | fi | |
| 228 | ||
| 229 | _diagnostics.add(diagnostic_message); | |
| 230 | si | |
| 231 | ||
| 232 | mark_poisoned() is | |
| 233 | is_poisoned = true; | |
| 234 | si | |
| 235 | ||
| 236 | mark_consumed_any() is | |
| 237 | has_consumed_any = true; | |
| 238 | si | |
| 239 | ||
| 240 | mark_consumed_error() is | |
| 241 | has_consumed_error = true; | |
| 242 | si | |
| 243 | ||
| 244 | clear_consumed_any() is | |
| 245 | has_consumed_any = false; | |
| 246 | si | |
| 247 | ||
| 248 | clear_consumed_error() is | |
| 249 | has_consumed_error = false; | |
| 250 | si | |
| 251 | ||
| 252 | clear(analysis_only: bool) is | |
| 253 | if analysis_only == false then | |
| 254 | _dirty = true; | |
| 255 | _diagnostics.clear(); | |
| 256 | else | |
| 257 | let n = LIST[DIAGNOSTIC_MESSAGE](_diagnostics.count); | |
| 258 | ||
| 259 | for d in _diagnostics do | |
| 260 | if !d.is_analysis then | |
| 261 | n.add(d); | |
| 262 | fi | |
| 263 | od | |
| 264 | ||
| 265 | _diagnostics = n; | |
| 266 | fi | |
| 267 | si | |
| 268 | ||
| 269 | clear_global_declaration_diagnostics() is | |
| 270 | let n = LIST[DIAGNOSTIC_MESSAGE](_diagnostics.count); | |
| 271 | ||
| 272 | for d in _diagnostics do | |
| 273 | if !d.is_analysis \/ d.is_compile_expressions then | |
| 274 | n.add(d); | |
| 275 | fi | |
| 276 | od | |
| 277 | ||
| 278 | _diagnostics = n; | |
| 279 | si | |
| 280 | ||
| 281 | // The complement of clear_global_declaration_diagnostics: drop | |
| 282 | // only the expression-level analysis diagnostics, keeping parse | |
| 283 | // and declaration-level ones. Used before an on-demand | |
| 284 | // compile-expressions re-walk of one file, which re-reports the | |
| 285 | // expression diagnostics but re-runs no earlier pass. | |
| 286 | clear_expression_diagnostics() is | |
| 287 | let n = LIST[DIAGNOSTIC_MESSAGE](_diagnostics.count); | |
| 288 | ||
| 289 | for d in _diagnostics do | |
| 290 | if !d.is_analysis \/ !d.is_compile_expressions then | |
| 291 | n.add(d); | |
| 292 | fi | |
| 293 | od | |
| 294 | ||
| 295 | _diagnostics = n; | |
| 296 | si | |
| 297 | ||
| 298 | write_all_diagnostics(writer: TextWriter, formatter: DiagnosticFormatter) is | |
| 299 | let written_any mut = false; | |
| 300 | ||
| 301 | for diagnostic in _diagnostics do | |
| 302 | let formatted = formatter.format(diagnostic); | |
| 303 | ||
| 304 | if formatted? then | |
| 305 | written_any = true; | |
| 306 | writer.write(formatted); | |
| 307 | writer.write("\n"); | |
| 308 | fi | |
| 309 | od | |
| 310 | ||
| 311 | if !written_any /\ formatter.need_clear_errors then | |
| 312 | // clear errors in the client | |
| 313 | writer.write(_path); | |
| 314 | writer.write("\n"); | |
| 315 | fi | |
| 316 | si | |
| 317 | ||
| 318 | write_filtered_diagnostics(writer: TextWriter, formatter: DiagnosticFormatter) is | |
| 319 | if _diagnostics |> filter(d => !d.is_analysis) |> count() == 0 then | |
| 320 | if formatter.need_clear_errors then | |
| 321 | // clear errors in the client | |
| 322 | writer.write(_path); | |
| 323 | writer.write("\n"); | |
| 324 | fi | |
| 325 | ||
| 326 | return; | |
| 327 | fi | |
| 328 | ||
| 329 | // only write the first 15 syntax diagnostics per source file, on the assumption that | |
| 330 | // the first few are probably the root cause of the whole error cascade. Note this | |
| 331 | // relies on errors being written in the order they are discovered, which is guaranteed | |
| 332 | // for syntax errors, but not for analysis errors. | |
| 333 | for diagnostic in _diagnostics |> filter(d => !d.is_analysis) |> take(15) do | |
| 334 | let formatted = formatter.format(diagnostic); | |
| 335 | ||
| 336 | if formatted? then | |
| 337 | writer.write(formatted); | |
| 338 | writer.write("\n"); | |
| 339 | fi | |
| 340 | od | |
| 341 | si | |
| 342 | si | |
| 343 | ||
| 344 | class DIAGNOSTICS_STATE is | |
| 345 | count: int => _diagnostics_by_source_path.values |> reduce(0, (r, d) => r + d.count); | |
| 346 | is_poisoned: bool => _diagnostics_by_source_path.values |> any(d => d.is_poisoned); | |
| 347 | has_consumed_error: bool; | |
| 348 | has_consumed_any: bool; | |
| 349 | any_errors: bool => _diagnostics_by_source_path.values |> any(d => d.any_errors); | |
| 350 | any_warnings: bool => _diagnostics_by_source_path.values |> any(d => d.any_warnings); | |
| 351 | ||
| 352 | paths_with_errors: Iterable[string] is | |
| 353 | let result = LIST[string](); | |
| 354 | ||
| 355 | for pair in _diagnostics_by_source_path do | |
| 356 | if pair.value.any_errors then | |
| 357 | result.add(pair.key); | |
| 358 | fi | |
| 359 | od | |
| 360 | ||
| 361 | return result; | |
| 362 | si | |
| 363 | ||
| 364 | // A parse error typically produces a flood of downstream semantic | |
| 365 | // errors; when one is present and the total error count is high, | |
| 366 | // the filtered writer shows only the pre-analysis diagnostics on | |
| 367 | // the assumption that the rest are noise. Both arms count errors | |
| 368 | // only: warnings and hints are not cascade evidence, and a project | |
| 369 | // can carry standing warnings (e.g. name-convention warnings, which | |
| 370 | // are logged pre-analysis) without losing its semantic diagnostics. | |
| 371 | is_possible_error_cascade: bool => | |
| 372 | (_diagnostics_by_source_path.values |> any(d => d.syntax_error_count > 0)) /\ | |
| 373 | (_diagnostics_by_source_path.values |> reduce(0, (r, d) => r + d.error_count) > 30); | |
| 374 | ||
| 375 | _diagnostics_by_source_path: MutableMap[string, DIAGNOSTICS_LIST]; | |
| 376 | ||
| 377 | init() is | |
| 378 | _diagnostics_by_source_path = MAP[string, DIAGNOSTICS_LIST](); | |
| 379 | si | |
| 380 | ||
| 381 | clear() is | |
| 382 | for i in _diagnostics_by_source_path.values do | |
| 383 | i.clear(false); | |
| 384 | od | |
| 385 | ||
| 386 | init(); | |
| 387 | si | |
| 388 | ||
| 389 | clear(source_path: string, analysis_only: bool) is | |
| 390 | get_diagnostics_list(source_path).clear(analysis_only); | |
| 391 | si | |
| 392 | ||
| 393 | clear_global_declaration_diagnostics(source_path: string) is | |
| 394 | get_diagnostics_list(source_path).clear_global_declaration_diagnostics(); | |
| 395 | si | |
| 396 | ||
| 397 | clear_expression_diagnostics(source_path: string) is | |
| 398 | get_diagnostics_list(source_path).clear_expression_diagnostics(); | |
| 399 | si | |
| 400 | ||
| 401 | get_diagnostics_list(source_path: string) -> DIAGNOSTICS_LIST is | |
| 402 | let diagnostics_for_path: DIAGNOSTICS_LIST mut; | |
| 403 | ||
| 404 | if !_diagnostics_by_source_path.try_get_value(source_path, diagnostics_for_path ref) then | |
| 405 | diagnostics_for_path = DIAGNOSTICS_LIST(source_path); | |
| 406 | ||
| 407 | _diagnostics_by_source_path.add(source_path, diagnostics_for_path); | |
| 408 | fi | |
| 409 | ||
| 410 | return diagnostics_for_path; | |
| 411 | si | |
| 412 | ||
| 413 | add_diagnostic_message(source_path: string, message: DIAGNOSTIC_MESSAGE) is | |
| 414 | get_diagnostics_list(source_path).add(message); | |
| 415 | si | |
| 416 | ||
| 417 | mark_poisoned(source_path: string) is | |
| 418 | get_diagnostics_list(source_path).mark_poisoned(); | |
| 419 | si | |
| 420 | ||
| 421 | mark_consumed_error() is | |
| 422 | has_consumed_error = true; | |
| 423 | si | |
| 424 | ||
| 425 | mark_consumed_any() is | |
| 426 | has_consumed_any = true; | |
| 427 | si | |
| 428 | ||
| 429 | clear_consumed_error() is | |
| 430 | for i in _diagnostics_by_source_path.values do | |
| 431 | i.clear_consumed_error(); | |
| 432 | od | |
| 433 | si | |
| 434 | ||
| 435 | clear_consumed_any() is | |
| 436 | for i in _diagnostics_by_source_path.values do | |
| 437 | i.clear_consumed_any(); | |
| 438 | od | |
| 439 | si | |
| 440 | ||
| 441 | merge(state: DIAGNOSTICS_STATE) is | |
| 442 | for i in state._diagnostics_by_source_path do | |
| 443 | add_diagnostic_messages(i.key, i.value.diagnostics); | |
| 444 | ||
| 445 | if i.value.is_poisoned then | |
| 446 | mark_poisoned(i.key); | |
| 447 | fi | |
| 448 | od | |
| 449 | ||
| 450 | if state.has_consumed_error then | |
| 451 | mark_consumed_error(); | |
| 452 | fi | |
| 453 | ||
| 454 | if state.has_consumed_any then | |
| 455 | mark_consumed_any(); | |
| 456 | fi | |
| 457 | si | |
| 458 | ||
| 459 | add_diagnostic_messages(source_path: string, diagnostics: Iterable[DIAGNOSTIC_MESSAGE]) is | |
| 460 | let list = get_diagnostics_list(source_path); | |
| 461 | ||
| 462 | for d in diagnostics do | |
| 463 | list.add(d); | |
| 464 | od | |
| 465 | si | |
| 466 | ||
| 467 | write_all_diagnostics(writer: TextWriter, formatter: DiagnosticFormatter) is | |
| 468 | for list in _diagnostics_by_source_path.values do | |
| 469 | list.write_all_diagnostics(writer, formatter); | |
| 470 | od | |
| 471 | si | |
| 472 | ||
| 473 | write_filtered_diagnostics(writer: TextWriter, formatter: DiagnosticFormatter) is | |
| 474 | for list in _diagnostics_by_source_path.values do | |
| 475 | list.write_filtered_diagnostics(writer, formatter); | |
| 476 | od | |
| 477 | si | |
| 478 | si | |
| 479 | ||
| 480 | class DIAGNOSTICS_STORE: Logger is | |
| 481 | _states: STACK[DIAGNOSTICS_STATE]; | |
| 482 | // The bottom, never-speculated diagnostics state. Lexer errors are | |
| 483 | // written straight here (see lexer_error). | |
| 484 | _base_state: DIAGNOSTICS_STATE; | |
| 485 | _suppressed_codes: Collections.SET[string]; | |
| 486 | _suppression_regions: SUPPRESSION_REGIONS; | |
| 487 | _all_warnings_are_errors: bool; | |
| 488 | _error_codes: Collections.SET[string]; | |
| 489 | _hint_codes: Collections.SET[string]; | |
| 490 | _info_codes: Collections.SET[string]; | |
| 491 | ||
| 492 | is_poisoned: bool => _states.peek().is_poisoned; | |
| 493 | has_consumed_error: bool => _states.peek().has_consumed_error; | |
| 494 | has_consumed_any: bool => _states.peek().has_consumed_any; | |
| 495 | ||
| 496 | error_count: int => _states.peek().count; | |
| 497 | any_errors: bool => _states.peek().any_errors; | |
| 498 | any_warnings: bool => _states.peek().any_warnings; | |
| 499 | paths_with_errors: Iterable[string] => _states.peek().paths_with_errors; | |
| 500 | ||
| 501 | is_clean: bool => !any_errors /\ !has_consumed_error /\ !has_consumed_any; | |
| 502 | ||
| 503 | is_analysis: bool public; | |
| 504 | is_compile_expressions: bool public; | |
| 505 | ||
| 506 | _open_files: Collections.SET[string]; | |
| 507 | ||
| 508 | depth: int => _states.count; | |
| 509 | ||
| 510 | init() is | |
| 511 | _states = STACK(); | |
| 512 | _base_state = DIAGNOSTICS_STATE(); | |
| 513 | _states.push(_base_state); | |
| 514 | _suppressed_codes = Collections.SET[string](); | |
| 515 | _suppression_regions = SUPPRESSION_REGIONS(); | |
| 516 | _error_codes = Collections.SET[string](); | |
| 517 | _hint_codes = Collections.SET[string](); | |
| 518 | _info_codes = Collections.SET[string](); | |
| 519 | _open_files = Collections.SET[string](); | |
| 520 | si | |
| 521 | ||
| 522 | set_open_files(paths: Collections.Iterable[string]) -> bool is | |
| 523 | let previous = _open_files; | |
| 524 | _open_files = Collections.SET[string](); | |
| 525 | ||
| 526 | let has_new_path mut = false; | |
| 527 | ||
| 528 | for path in paths do | |
| 529 | _open_files.add(path); | |
| 530 | ||
| 531 | if !previous.contains(path) then | |
| 532 | has_new_path = true; | |
| 533 | fi | |
| 534 | od | |
| 535 | ||
| 536 | return has_new_path \/ _open_files.count != previous.count; | |
| 537 | si | |
| 538 | ||
| 539 | is_file_open(path: string?) -> bool => path? /\ _open_files.contains(path); | |
| 540 | ||
| 541 | suppress(code: string) is | |
| 542 | _suppressed_codes.add(code); | |
| 543 | si | |
| 544 | ||
| 545 | is_suppressed(code: string?) -> bool => | |
| 546 | code? /\ _suppressed_codes.contains(code); | |
| 547 | ||
| 548 | set_all_warnings_are_errors(value: bool) is | |
| 549 | _all_warnings_are_errors = value; | |
| 550 | si | |
| 551 | ||
| 552 | promote_to_error(code: string) is | |
| 553 | _error_codes.add(code); | |
| 554 | si | |
| 555 | ||
| 556 | is_promoted_to_error(code: string?) -> bool => | |
| 557 | _all_warnings_are_errors \/ (code? /\ _error_codes.contains(code)); | |
| 558 | ||
| 559 | demote_to_hint(code: string) is | |
| 560 | _hint_codes.add(code); | |
| 561 | si | |
| 562 | ||
| 563 | is_demoted_to_hint(code: string?) -> bool => | |
| 564 | code? /\ _hint_codes.contains(code); | |
| 565 | ||
| 566 | demote_to_info(code: string) is | |
| 567 | _info_codes.add(code); | |
| 568 | si | |
| 569 | ||
| 570 | is_demoted_to_info(code: string?) -> bool => | |
| 571 | code? /\ _info_codes.contains(code); | |
| 572 | ||
| 573 | want_hint_for(location: LOCATION?) -> bool => | |
| 574 | is_analysis /\ location? /\ is_file_open(location.file_name); | |
| 575 | ||
| 576 | register_suppression_region(region: LOCATION, code: string) is | |
| 577 | _suppression_regions.register(region, code); | |
| 578 | si | |
| 579 | ||
| 580 | clear_suppression_regions(path: string) is | |
| 581 | _suppression_regions.clear(path); | |
| 582 | si | |
| 583 | ||
| 584 | clear_suppression_regions() is | |
| 585 | _suppression_regions.clear(); | |
| 586 | si | |
| 587 | ||
| 588 | is_suppressed(code: string?, location: LOCATION) -> bool => | |
| 589 | code? /\ | |
| 590 | (_suppressed_codes.contains(code) \/ _suppression_regions.contains(code, location)); | |
| 591 | ||
| 592 | start_analysis() is | |
| 593 | is_analysis = true; | |
| 594 | si | |
| 595 | ||
| 596 | end_analysis() is | |
| 597 | is_analysis = false; | |
| 598 | is_compile_expressions = false; | |
| 599 | si | |
| 600 | ||
| 601 | set_is_compiling_expressions(value: bool) is | |
| 602 | is_compile_expressions = value; | |
| 603 | si | |
| 604 | ||
| 605 | only: DIAGNOSTICS_STATE is | |
| 606 | assert _states.count == 1 else "expected exactly one stacked diagnostics state, found {_states.count}"; | |
| 607 | ||
| 608 | return _states.peek(); | |
| 609 | si | |
| 610 | ||
| 611 | top: DIAGNOSTICS_STATE is | |
| 612 | assert _states.count >= 1 else "expected at least one stacked diagnostics state"; | |
| 613 | ||
| 614 | return _states.peek(); | |
| 615 | si | |
| 616 | ||
| 617 | pop() -> DIAGNOSTICS_STATE is | |
| 618 | assert _states.count >= 1 else "expected at least one stacked diagnostics state"; | |
| 619 | ||
| 620 | return _states.pop(); | |
| 621 | si | |
| 622 | ||
| 623 | clear(source_path: string, analysis_only: bool) is | |
| 624 | only.clear(source_path, analysis_only); | |
| 625 | si | |
| 626 | ||
| 627 | clear_global_declaration_diagnostics(source_path: string) is | |
| 628 | only.clear_global_declaration_diagnostics(source_path); | |
| 629 | si | |
| 630 | ||
| 631 | clear_expression_diagnostics(source_path: string) is | |
| 632 | only.clear_expression_diagnostics(source_path); | |
| 633 | si | |
| 634 | ||
| 635 | speculate() is | |
| 636 | _states.push(DIAGNOSTICS_STATE()); | |
| 637 | si | |
| 638 | ||
| 639 | roll_back() -> DIAGNOSTICS_STATE => | |
| 640 | pop(); | |
| 641 | ||
| 642 | commit() is | |
| 643 | let to_merge = pop(); | |
| 644 | ||
| 645 | top.merge(to_merge); | |
| 646 | si | |
| 647 | ||
| 648 | mark() -> int => _states.count; | |
| 649 | release(mark: int) is | |
| 650 | while _states.count > mark do | |
| 651 | pop(); | |
| 652 | od | |
| 653 | si | |
| 654 | ||
| 655 | speculate_then_commit() -> LOGGER_SPECULATE_THEN_COMMIT => | |
| 656 | LOGGER_SPECULATE_THEN_COMMIT(self); | |
| 657 | ||
| 658 | speculate_then_backtrack() -> LOGGER_SPECULATE_THEN_BACKTRACK => | |
| 659 | LOGGER_SPECULATE_THEN_BACKTRACK(self); | |
| 660 | ||
| 661 | mark_then_release() -> MARK_THEN_RELEASE => | |
| 662 | MARK_THEN_RELEASE(self); | |
| 663 | ||
| 664 | merge(state: DIAGNOSTICS_STATE) is | |
| 665 | top.merge(state); | |
| 666 | si | |
| 667 | ||
| 668 | write_all_diagnostics(writer: TextWriter, formatter: DiagnosticFormatter) is | |
| 669 | let state = only; | |
| 670 | ||
| 671 | if formatter.want_filter_error_cascades /\ state.is_possible_error_cascade then | |
| 672 | state.write_filtered_diagnostics(writer, formatter); | |
| 673 | else | |
| 674 | state.write_all_diagnostics(writer, formatter); | |
| 675 | fi | |
| 676 | si | |
| 677 | ||
| 678 | poison(location: LOCATION) is | |
| 679 | top.mark_poisoned(location.file_name); | |
| 680 | si | |
| 681 | ||
| 682 | mark_consumed_error() is | |
| 683 | top.mark_consumed_error(); | |
| 684 | si | |
| 685 | ||
| 686 | mark_consumed_any() is | |
| 687 | top.mark_consumed_any(); | |
| 688 | si | |
| 689 | ||
| 690 | mark_consumed_any_if(consumed: bool) is | |
| 691 | if consumed then | |
| 692 | top.mark_consumed_any(); | |
| 693 | fi | |
| 694 | si | |
| 695 | ||
| 696 | clear_consumed_any() is | |
| 697 | top.clear_consumed_any(); | |
| 698 | si | |
| 699 | ||
| 700 | clear_consumed_error() is | |
| 701 | top.clear_consumed_error(); | |
| 702 | si | |
| 703 | ||
| 704 | exception(location: LOCATION, exception: Exception, message: string) is | |
| 705 | debug_always("{location} exception: {exception.to_string().replace_line_endings(" ")}"); | |
| 706 | ||
| 707 | if _states.count == 0 then | |
| 708 | debug_always("diagnostics store: exception depth {_states.count}: {location}: {message}: {exception}"); | |
| 709 | fi | |
| 710 | ||
| 711 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.EXCEPTION, location, "{message}: {exception}")); | |
| 712 | si | |
| 713 | ||
| 714 | fatal(location: LOCATION, message: string) is | |
| 715 | debug_always("{location}: fatal: {message}"); | |
| 716 | ||
| 717 | debug_always("diagnostics store: fatal depth {_states.count}: {location}: {message}"); | |
| 718 | ||
| 719 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.FATAL, location, message)); | |
| 720 | si | |
| 721 | ||
| 722 | error(location: LOCATION, message: string) is | |
| 723 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.ERROR, location, message)); | |
| 724 | si | |
| 725 | ||
| 726 | // A lexical error comes from the tokenizer, which reads each source | |
| 727 | // position exactly once — parser speculation replays buffered tokens, | |
| 728 | // it never re-lexes. So a lexer error is produced once no matter how | |
| 729 | // the parser speculates over those tokens. It is written to the base | |
| 730 | // state rather than the current speculative one, so a parse that reads | |
| 731 | // the tokens speculatively and then backtracks does not roll it back | |
| 732 | // and lose it. Unlike a parse error — which the parser regenerates on | |
| 733 | // every re-walk and so must roll back to avoid duplicates — a lexer | |
| 734 | // error cannot duplicate, because nothing regenerates it. | |
| 735 | lexer_error(location: LOCATION, message: string) is | |
| 736 | _base_state.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.ERROR, location, message)); | |
| 737 | si | |
| 738 | ||
| 739 | warn(location: LOCATION, message: string) is | |
| 740 | let severity = | |
| 741 | if _all_warnings_are_errors then | |
| 742 | DiagnosticSeverity.ERROR | |
| 743 | else | |
| 744 | DiagnosticSeverity.WARN | |
| 745 | fi; | |
| 746 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, severity, location, message)); | |
| 747 | si | |
| 748 | ||
| 749 | warn(location: LOCATION, code: string, message: string) is | |
| 750 | if is_suppressed(code, location) then | |
| 751 | return; | |
| 752 | fi | |
| 753 | ||
| 754 | if is_demoted_to_hint(code) then | |
| 755 | // Emitted as an editor-only hint under the same gate as a | |
| 756 | // native hint: dropped in batch compilation, surfaced in | |
| 757 | // analysis mode only for a file the client has open. | |
| 758 | if !want_hint_for(location) then | |
| 759 | return; | |
| 760 | fi | |
| 761 | ||
| 762 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.HINT, location, code, message)); | |
| 763 | ||
| 764 | return; | |
| 765 | fi | |
| 766 | ||
| 767 | if is_demoted_to_info(code) then | |
| 768 | // Unlike a hint, info stays a normal batch-visible | |
| 769 | // diagnostic; it is not subject to the editor-only gate. | |
| 770 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.INFO, location, code, message)); | |
| 771 | ||
| 772 | return; | |
| 773 | fi | |
| 774 | ||
| 775 | let severity = | |
| 776 | if is_promoted_to_error(code) then | |
| 777 | DiagnosticSeverity.ERROR | |
| 778 | else | |
| 779 | DiagnosticSeverity.WARN | |
| 780 | fi; | |
| 781 | ||
| 782 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, severity, location, code, message)); | |
| 783 | si | |
| 784 | ||
| 785 | info(location: LOCATION, message: string) is | |
| 786 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.INFO, location, message)); | |
| 787 | si | |
| 788 | ||
| 789 | info(location: LOCATION, code: string, message: string) is | |
| 790 | if is_suppressed(code, location) then | |
| 791 | return; | |
| 792 | fi | |
| 793 | ||
| 794 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.INFO, location, code, message)); | |
| 795 | si | |
| 796 | ||
| 797 | hint(location: LOCATION, message: string) is | |
| 798 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.HINT, location, message)); | |
| 799 | si | |
| 800 | ||
| 801 | hint(location: LOCATION, code: string, message: string) is | |
| 802 | if is_suppressed(code, location) then | |
| 803 | return; | |
| 804 | fi | |
| 805 | ||
| 806 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.HINT, location, code, message)); | |
| 807 | si | |
| 808 | ||
| 809 | inlay(location: LOCATION, code: string, label: string, detail: string?) is | |
| 810 | if is_suppressed(code, location) then | |
| 811 | return; | |
| 812 | fi | |
| 813 | ||
| 814 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.INLAY, location, code, label, detail)); | |
| 815 | si | |
| 816 | ||
| 817 | inlays_for(path: string) -> Collections.Iterable[DIAGNOSTIC_MESSAGE] => | |
| 818 | top.get_diagnostics_list(path).diagnostics |> filter(d => d.is_inlay); | |
| 819 | ||
| 820 | poison(location: LOCATION, message: string) is | |
| 821 | top.add_diagnostic_message(location.file_name, DIAGNOSTIC_MESSAGE(is_analysis, is_compile_expressions, DiagnosticSeverity.FATAL, location, message)); | |
| 822 | top.mark_poisoned(location.file_name); | |
| 823 | si | |
| 824 | ||
| 825 | write_poison_messages() is | |
| 826 | if is_poisoned then | |
| 827 | debug_always("internal compiler error"); | |
| 828 | IO.Std.error.flush(); | |
| 829 | fi | |
| 830 | si | |
| 831 | si | |
| 832 | si |