All files / TRANSPILE/3-Render/codegen TypeValidator.ts

90% Statements 27/30
90.9% Branches 30/33
100% Functions 4/4
90% Lines 27/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270                                                                                                                                                                                        1566x 1566x       570x 570x 570x                   996x       73x 73x     923x   923x 42x         42x 14x 14x         909x     1566x                 811x     98x                     42x 42x 14x   28x                 192x                                         192x                     2x 1x   1x                                                                                                                              
/**
 * TypeValidator - Handles compile-time validation of types, assignments, and control flow
 * Static class using CodeGenState for all state access.
 * Issue #63: Validation logic separated for independent testing
 */
import type ISourcePosition from "../../../utils/types/ISourcePosition";
import AdrProvenance from "../../../instrumentation/AdrProvenance";
// SonarCloud S3776: Extracted literal parsing to reduce complexity
import QualifiedCName from "../../../utils/QualifiedCName";
import QualifiedNameGenerator from "../../../utils/QualifiedNameGenerator";
import type TranspileState from "../../TranspileState";
 
/**
 * TypeValidator class - validates types, assignments, and control flow at compile time.
 * All methods are static - uses CodeGenState for state access.
 */
class TypeValidator {
  // ========================================================================
  // Include Validation (ADR-010) -- relocated to pass 2.1 (#1322)
  // ========================================================================
  //
  // `validateIncludeNotImplementationFile` (E0503) and
  // `validateIncludeNoCnxAlternative` (E0504, both its quoted and its angle
  // branch) stood here, reached from `CodeGenerator.processIncludeDirectives`
  // with a line number threaded in as a NUMBER, spent on `Line N` prose while
  // the diagnostic itself reported `1:0`. `IncludeDirectiveAnalyzer` decides
  // both at the directive's own position.
  //
  // The angle branch also took its search path from
  // `IncludeDiscovery.discoverIncludePaths`, a SECOND derivation of a list
  // discovery had already built with the `--include` directories in it. The
  // two agreed only where `--include` was unused; 2.1 reads discovery's own
  // list, so there is one derivation.
 
  // #1322: ADR-034's literal-overflow check is E0881 in pass 2.1.
  //
  // `validateBitmapFieldLiteral` stood here and was reached only from the
  // bitmap assignment handler, which had already resolved the field. The rule
  // is about the VALUE and the field's width, both of which the parse tree and
  // the per-file bitmap layouts carry, so it needs no handler to have run
  // first -- and asking it there meant it could never see a write reached by
  // any other path.
 
  // ========================================================================
  // Array Bounds Validation (ADR-036)
  // ========================================================================
 
  // #1322: ADR-036's constant index bounds check (E0854) is in pass 2.1,
  // asked at every subscript of an expression or a target through one prefix
  // walk. It was reached from three codegen paths that each resolved the
  // array's name their own way, and a struct field's dimensions were never
  // among them.
  // #1322: ADR-029's callback rules are E0879 and E0880 in pass 2.1.
  //
  // `validateCallbackAssignment` and `callbackSignaturesMatch` stood here and
  // compared `ICallbackTypeInfo`, whose `isConst` is `declared || inferred`.
  // The inferred half is #268 auto-const -- a 2.2 Plan fact about whether a
  // BODY modifies a parameter -- so the check could not move as written, and
  // it protected nothing: it read `getUnmodifiedParameters()` before
  // `modifiedParameters` was filled, so both sides came back "unmodified" and
  // the const comparison was vacuous. 2.1 compares the DECLARED signature,
  // which 1.4 Resolve settles onto the symbol.
 
  // ========================================================================
  // Const Assignment Validation (ADR-013)
  // ========================================================================
 
  // #1322: ADR-013's `checkConstAssignment` and `isConstValue` are E0877 and
  // E0878 in pass 2.1, decided once from the frames and the program's symbols
  // rather than from `currentParameters` and the type registry.
  /**
   * The C name a bare identifier emits under, or null to leave it as written.
   *
   * #1668 (C7): which declaration a value name means is the binder's -- local,
   * then scope, then global (ADR-057) -- at the reference's position. This
   * used to take a per-function set of local names, which could not tell a
   * block's local from its sibling block's, and ask a registry keyed by
   * spelling whether a global existed. Function, enum, struct and register
   * names are not value bindings and keep their own checks below.
   *
   * @param at Position of the reference. Its line also records #1241
   *   provenance: an ADR-057 resolution is invisible to the scope-context
   *   matrix otherwise, because a successful resolution emits no diagnostic
   *   to take one from. Recorded HERE rather than at the three callers,
   *   which are required not to re-derive this decision.
   */
  static resolveBareIdentifier(
    identifier: string,
    at: ISourcePosition,
    isKnownStruct: (name: string) => boolean,
    state: TranspileState,
  ): string | null {
    const binding = state.bindingAt(null, identifier, at);
    if (binding?.kind === "local") {
      // ADR-057: a local normally emits under its own name (null = "leave it
      // alone"). One that shadows a file-scope symbol was given a distinct C
      // identifier at its declaration, and every reference must follow it.
      const emitted = state.emittedLocalName(identifier);
      Eif (emitted === identifier) {
        return null;
      }
      // The rename IS ADR-057's shadowing rule firing; a local that shadows
      // nothing is the rule declining to act, which is not evidence of it.
      AdrProvenance.record("057", at.line);
      return emitted;
    }
 
    // A scope member, by the binder's middle tier: a variable, or a function
    // named as a value (#1760 review)
    if (
      (binding?.kind === "variable" || binding?.kind === "function") &&
      binding.symbol.scopePath !== ""
    ) {
      AdrProvenance.record("057", at.line);
      return binding.symbol.fullyQualifiedCName;
    }
 
    const currentScopePath = state.currentScopePath;
 
    if (currentScopePath) {
      const scopeResolved = TypeValidator._resolveScopeMember(
        identifier,
        currentScopePath,
        state,
      );
      if (scopeResolved) {
        AdrProvenance.record("057", at.line);
        return scopeResolved;
      }
    }
 
    const isGlobalValue =
      binding?.kind === "variable" ||
      binding?.kind === "function" ||
      binding?.kind === "foreign";
    if (
      isGlobalValue ||
      TypeValidator._isKnownGlobalIdentifier(
        identifier,
        currentScopePath,
        isKnownStruct,
        state,
      )
    ) {
      return currentScopePath ? identifier : null;
    }
 
    return null;
  }
 
  private static _resolveScopeMember(
    identifier: string,
    currentScopePath: string,
    state: TranspileState,
  ): string | null {
    // #1295: getScopeMembers is keyed by the scope's dotted source path.
    // A member the binder gives no value binding: a type. Its variables and
    // functions were answered above, from the binding (#1760 review).
    const scopeMembers = state.getScopeMembers(currentScopePath);
    if (scopeMembers?.has(identifier)) {
      return QualifiedNameGenerator.forMember(currentScopePath, identifier);
    }
    return null;
  }
 
  private static _isKnownGlobalIdentifier(
    identifier: string,
    currentScopePath: string,
    isKnownStruct: (name: string) => boolean,
    state: TranspileState,
  ): boolean {
    Iif (
      state.knownFunctions.has(identifier) &&
      // #1295: pass the PATH, not its leaf. `isInScope` needs no help encoding
      // it -- `prefixFor` runs the path through `toParts`, which splits on the
      // source separator, so `Outer.Inner` becomes the prefix `Outer__Inner__`
      // where `leafOf` gave `Inner__`. File scope is handled before that, by
      // `isInScope`'s own `if (!scopeName) return false`: `prefixFor("")` would
      // otherwise return the bogus `"__"`.
      //
      // NO GATE COVERS THIS CALL SITE -- review is what catches it. An earlier
      // revision of this comment claimed the scope-join inventory did, which is
      // the guard-that-cannot-fail shape CLAUDE.md flags: that scan matches the
      // single literal `QualifiedCName.fromParts([` and inspects only the first
      // ARRAY ELEMENT, so `isInScope(...)`, `prefixFor(...)`, and a
      // `ScopeUtils.leafOf(...)` passed as an argument here are all invisible to
      // it. Verified: re-inlining the leaf reddens nothing in that check.
      !QualifiedCName.isInScope(identifier, currentScopePath)
    ) {
      return true;
    }
 
    return (
      state.symbols!.knownEnums.has(identifier) ||
      isKnownStruct(identifier) ||
      state.symbols!.knownRegisters.has(identifier)
    );
  }
 
  static resolveForMemberAccess(
    identifier: string,
    state: TranspileState,
  ): string | null {
    if (state.symbols!.knownScopes.has(identifier)) {
      return identifier;
    }
    return null;
  }
 
  // ========================================================================
  // Critical Section Validation (ADR-050)
  // ========================================================================
 
  // #1322: `validateNoEarlyExits` and its four private helpers are gone. The
  // rule is E0853 in pass 2.1, where a tree walk reaches every statement the
  // grammar can nest inside a `critical` block.
  //
  // The recursion here ENUMERATED the kinds it descended into -- return, if,
  // while, for, do-while -- and omitted `switch`, so a `return` in a switch
  // case compiled clean and emitted C that returns between
  // `__cnx_disable_irq()` and `__cnx_set_PRIMASK()`. On device, interrupts stay
  // off. A walk does not enumerate, so it cannot have that hole.
 
  // ========================================================================
  // Switch Statement Validation (ADR-025)
  // ========================================================================
 
  // #1322: ADR-025's switch rules are E0711-E0714 in pass 2.1 --
  // `validateSwitchStatement` and the three helpers only it used are gone.
  // All five throws reached the user as `1:0`, which seven fixtures under
  // `tests/switch/` asserted verbatim. Nothing here needed a fact the
  // analyzers could not already see: `knownEnums` and `enumMembers` are on the
  // per-file symbol view, and the clause count, the labels and `default(N)`
  // are in the parse tree. They lived here because this is where the switch
  // was being WRITTEN, not because this is where the facts were.
 
  // #1322: `validateNoNestedTernary` is gone. ADR-022's rule is E0710 in pass
  // 2.1, asked of the parse tree.
  //
  // What stood here was a SUBSTRING TEST on the branch's source text --
  // `text.includes("?") && text.includes(":")` -- which rejected
  // `(n = 1) ? "a?b:c" : "plain"`, a legal ternary whose true branch is a
  // string literal containing both characters. A rule about syntax asking
  // about characters.
 
  // #1322: ADR-022's controlling-expression rule is E0701/E0702 in pass 2.1.
  // Eight methods stood here -- the boolean check, its three-level decomposition
  // of `||`/`&&`, the help-text builder, and the two function-call checks. The
  // rule is purely SYNTACTIC, so none of it needed anything codegen had; only
  // the help text asked a type question, and 2.1 asks it of the lexical frames,
  // which honour shadowing where a flat registry lookup does not.
 
  // #1322: ADR-068's always-true loop condition (E0707) is in pass 2.1, with
  // `for (;;)` and E0705 beside it. Five methods stood here -- the literal
  // slice's comparison reader, its number parser and the verdict -- all facts
  // of the parse tree that never needed codegen.
 
  // #1322: MISRA 12.2's shift-amount rule (E0873) is in pass 2.1, beside the
  // Rule 10.1 signed-operand rule it always belonged with. Five methods stood
  // here -- the width table, the literal amount evaluator and the two throws --
  // and the compound forms (`<<<-`, `>><-`) never reached them.
 
  // #1322: `validateIntegerAssignment` stood here -- ADR-024's literal-range,
  // narrowing and sign-change rules, reached through `AssignmentValidator`,
  // which caught the throw and prefixed `${line}:${col}` onto it. E0868/E0869
  // in pass 2.1 now.
}
 
export default TypeValidator;