Appearance
| 1 | namespace Logging is | |
| 2 | use Collections.Iterable | |
| 3 | use Collections.LIST | |
| 4 | ||
| 5 | // The analysis-mode counter vocabulary. | |
| 6 | // | |
| 7 | // Analysis mode reports named counters over the STATS request, and a | |
| 8 | // test asserts against them that no more than the expected amount of | |
| 9 | // compilation took place. That only works if the names are declared | |
| 10 | // in one place rather than spelled at each bump site: `declare_all` | |
| 11 | // creates every counter up front, so a name shows up in the report at | |
| 12 | // count zero as well as when it fires, and a test can enumerate the | |
| 13 | // whole vocabulary and check each name is exercised somewhere. | |
| 14 | // | |
| 15 | // Three families: | |
| 16 | // | |
| 17 | // file-pass:<pass> one file taken through one pass of the | |
| 18 | // ordinary build loop, or through the | |
| 19 | // on-demand expression compile | |
| 20 | // file-rewalk:<pass> one file's bodies taken through one pass by | |
| 21 | // the incremental body re-walk | |
| 22 | // subtree-pass:<pass> one changed or appended subtree taken | |
| 23 | // through one pass | |
| 24 | // | |
| 25 | // The distinction is the point: an interface-preserving edit should | |
| 26 | // show body re-walks and no whole-file passes. | |
| 27 | class WORK_COUNTERS is | |
| 28 | EDIT_PATH_BODY_REWALK: string static => "edit-path-body-rewalk" | |
| 29 | EDIT_PATH_INTERFACE_INCREMENTAL: string static => "edit-path-interface-incremental" | |
| 30 | EDIT_PATH_FULL_REBUILD: string static => "edit-path-full-rebuild" | |
| 31 | ||
| 32 | FILE_PASS_PREFIX: string static => "file-pass:" | |
| 33 | FILE_REWALK_PREFIX: string static => "file-rewalk:" | |
| 34 | SUBTREE_PASS_PREFIX: string static => "subtree-pass:" | |
| 35 | DECLINED_PREFIX: string static => "edit-declined-" | |
| 36 | ||
| 37 | // Why an EDIT was handled by the whole-project rebuild. Exactly | |
| 38 | // one of these is reported per rebuilt EDIT, so they sum to the | |
| 39 | // full-rebuild tally and the report says which guard was | |
| 40 | // responsible rather than only that some guard was. | |
| 41 | // | |
| 42 | // A guard inside the narrowing search declines a *route*, not the | |
| 43 | // edit: the search falls through to member replacement, which may | |
| 44 | // still succeed. So a reason is latched when its guard fires and | |
| 45 | // reported only if the edit ends up rebuilding. First latched | |
| 46 | // wins - the search rejects the narrowest route first and widens | |
| 47 | // as it falls through, so the first guard to fire is the one that | |
| 48 | // stopped the best route available. | |
| 49 | NOT_ELIGIBLE: string static => "not-eligible" | |
| 50 | NO_RETAINED_PARSE: string static => "no-retained-parse" | |
| 51 | MISSING_INTERFACE_SIGNATURE: string static => "missing-interface-signature" | |
| 52 | FILE_ROOT_NOT_A_LIST: string static => "file-root-not-a-list" | |
| 53 | FUNCTION_PAIRING_DESYNC: string static => "function-pairing-desync" | |
| 54 | LOCATION_REFRESH_DESYNC: string static => "location-refresh-desync" | |
| 55 | MULTIPLE_CHANGED_DEFINITIONS: string static => "multiple-changed-definitions" | |
| 56 | FILE_LEVEL_CHANGE: string static => "file-level-change" | |
| 57 | FEWER_DEFINITIONS: string static => "fewer-definitions" | |
| 58 | RETAINED_PREFIX_MISMATCH: string static => "retained-prefix-mismatch" | |
| 59 | NAMESPACE_NAME_MISMATCH: string static => "namespace-name-mismatch" | |
| 60 | CLASS_HEADER_MISMATCH: string static => "class-header-mismatch" | |
| 61 | NO_CLASS_SYMBOL: string static => "no-class-symbol" | |
| 62 | UNRESETTABLE_CLOSURE: string static => "unresettable-closure" | |
| 63 | OUTGOING_NOT_A_FUNCTION: string static => "outgoing-not-a-function" | |
| 64 | OUTGOING_HAS_OVERRIDERS: string static => "outgoing-has-overriders" | |
| 65 | OUTGOING_HAS_OVERRIDEES: string static => "outgoing-has-overridees" | |
| 66 | CROSS_FILE_NAMESPACE_REFERENCE: string static => "cross-file-namespace-reference" | |
| 67 | CROSS_FILE_UNKNOWN_FILE: string static => "cross-file-unknown-file" | |
| 68 | INCOMING_NOT_A_FUNCTION: string static => "incoming-not-a-function" | |
| 69 | TOP_LEVEL_STATEMENTS: string static => "top-level-statements" | |
| 70 | ||
| 71 | edit_paths: Iterable[string] static => | |
| 72 | [ | |
| 73 | EDIT_PATH_BODY_REWALK, | |
| 74 | EDIT_PATH_INTERFACE_INCREMENTAL, | |
| 75 | EDIT_PATH_FULL_REBUILD | |
| 76 | ] | |
| 77 | ||
| 78 | decline_reasons: Iterable[string] static => | |
| 79 | [ | |
| 80 | NOT_ELIGIBLE, | |
| 81 | NO_RETAINED_PARSE, | |
| 82 | MISSING_INTERFACE_SIGNATURE, | |
| 83 | FILE_ROOT_NOT_A_LIST, | |
| 84 | FUNCTION_PAIRING_DESYNC, | |
| 85 | LOCATION_REFRESH_DESYNC, | |
| 86 | MULTIPLE_CHANGED_DEFINITIONS, | |
| 87 | FILE_LEVEL_CHANGE, | |
| 88 | FEWER_DEFINITIONS, | |
| 89 | RETAINED_PREFIX_MISMATCH, | |
| 90 | NAMESPACE_NAME_MISMATCH, | |
| 91 | CLASS_HEADER_MISMATCH, | |
| 92 | NO_CLASS_SYMBOL, | |
| 93 | UNRESETTABLE_CLOSURE, | |
| 94 | OUTGOING_NOT_A_FUNCTION, | |
| 95 | OUTGOING_HAS_OVERRIDERS, | |
| 96 | OUTGOING_HAS_OVERRIDEES, | |
| 97 | CROSS_FILE_NAMESPACE_REFERENCE, | |
| 98 | CROSS_FILE_UNKNOWN_FILE, | |
| 99 | INCOMING_NOT_A_FUNCTION, | |
| 100 | TOP_LEVEL_STATEMENTS | |
| 101 | ] | |
| 102 | ||
| 103 | // Create every declared counter so the report carries the whole | |
| 104 | // vocabulary, including names that have not fired. Called once at | |
| 105 | // analyser startup. | |
| 106 | declare_all(timers: TIMERS) static is | |
| 107 | for name in edit_paths do | |
| 108 | timers.declare(name) | |
| 109 | od | |
| 110 | ||
| 111 | for reason in decline_reasons do | |
| 112 | timers.declare(declined_name(reason)) | |
| 113 | od | |
| 114 | si | |
| 115 | ||
| 116 | declined_name(reason: string) -> string static => | |
| 117 | "{DECLINED_PREFIX}{reason}" | |
| 118 | ||
| 119 | file_pass(timers: TIMERS, pass: string) static is | |
| 120 | timers.bump("{FILE_PASS_PREFIX}{pass}") | |
| 121 | si | |
| 122 | ||
| 123 | file_rewalk(timers: TIMERS, pass: string) static is | |
| 124 | timers.bump("{FILE_REWALK_PREFIX}{pass}") | |
| 125 | si | |
| 126 | ||
| 127 | subtree_pass(timers: TIMERS, pass: string) static is | |
| 128 | timers.bump("{SUBTREE_PASS_PREFIX}{pass}") | |
| 129 | si | |
| 130 | ||
| 131 | declined(timers: TIMERS, reason: string) static is | |
| 132 | timers.bump(declined_name(reason)) | |
| 133 | si | |
| 134 | si | |
| 135 | si |