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 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | 28x 380x 351x 2x 2x 2x 2x 2x 337x 20x 20x 19x 19x 19x 81x 81x 81x 67x 3x 64x 44x 64x 1x 63x 20x 43x 43x 78x 28x 28x 28x 28x 53x 53x 53x 53x 3x 3x 3x 3x 50x 50x 50x 50x 53x 53x 64x 64x 64x 53x 337x 337x 1694x 1694x 1694x 337x 5x 332x 2x 330x 2x 328x 53x 275x 10x 10x 265x 255x 255x 253x | /**
* Operand Type Resolver
*
* Resolves the declared type of an operand or an assignment target, following
* member and subscript chains: `flag`, `this.flag`, `sensor.ready`,
* `outer.inner.ready`, `flags[0]`.
*
* The essential-type rules need this in two places that used to answer it
* separately -- the expression side (is this operand essentially Boolean?) and
* the assignment side (is this target a bool?). Resolving both here keeps the
* answer identical whichever spelling reaches it, which is what Issue #1183
* review found missing: `ready + 1` was rejected while `this.ready + 1`,
* `sensor.ready / other`, and `flags[0] / flags[1]` were all accepted.
*
* Declared names come from the lexical scope frames; struct field types come
* from CodeGenState, which merges same-file and included-file structs and is
* populated before runAnalyzers (Transpiler `_transpileFile`).
*/
import { ParserRuleContext, ParseTree } from "antlr4ng";
import * as Parser from "../parser/grammar/CNextParser";
import IScopeFrame from "./types/IScopeFrame";
import ScopeFrameResolver from "./ScopeFrameResolver";
import CodeGenState from "../../state/CodeGenState";
import QualifiedCName from "../../../utils/QualifiedCName";
import ScopeUtils from "../../../utils/ScopeUtils";
/** One step of a member/subscript/call chain. */
interface IChainStep {
readonly member: string | null;
readonly isSubscript: boolean;
readonly isCall: boolean;
}
/** The one type name that is essentially Boolean (MISRA C:2012 Rule 10.1). */
const BOOLEAN_TYPE_NAME = "bool";
class OperandTypeResolver {
private readonly scopes: ScopeFrameResolver;
constructor(scopes: ScopeFrameResolver) {
this.scopes = scopes;
}
/**
* Whether a resolved type is essentially Boolean. Callers test through this
* rather than comparing to a literal, so what counts as Boolean is decided
* once.
*/
public static isBooleanType(typeName: string | null): boolean {
return typeName === BOOLEAN_TYPE_NAME;
}
/**
* Type of a ternary: the type its ARMS agree on. The condition does not
* contribute -- it is always Boolean and says nothing about the result.
*
* The arms are reached through `orExpression()`, never `getChild(i)`: the
* condition is parenthesized, so child 0 is `(` and an index-based skip
* silently does nothing (CLAUDE.md). A real ternary has exactly three
* orExpression children; anything else is a pass-through this never sees.
*/
private typeOfTernary(
ctx: Parser.TernaryExpressionContext,
frame: IScopeFrame,
): string | null {
const branches = ctx.orExpression();
Iif (branches.length !== 3) return null;
const whenTrue = this.typeOfOperand(branches[1], frame);
const whenFalse = this.typeOfOperand(branches[2], frame);
// Arms that disagree are a separate defect; report no type rather than
// guessing which one the expression takes.
return whenTrue !== null && whenTrue === whenFalse ? whenTrue : null;
}
/**
* Whether a node is an applied operator whose result is Boolean: `||`, `&&`,
* `=` / `!=`, or a relational comparison.
*
* Only an APPLIED one reaches here. Each of these grammar levels is a
* pass-through when it holds a single operand, and the caller descends
* through those before asking, so a node arriving here with more than one
* child is a real operator application.
*/
private static isBooleanValuedOperator(node: ParseTree): boolean {
return (
node instanceof Parser.OrExpressionContext ||
node instanceof Parser.AndExpressionContext ||
node instanceof Parser.EqualityExpressionContext ||
node instanceof Parser.RelationalExpressionContext
);
}
/**
* Strip ONE array dimension per subscript, leading dimension first, matching
* the C the transpiler emits: `bool[2][3] flags` becomes `bool flags[2][3]`,
* so `flags[0]` is `bool[3]` and `flags[0][1]` is `bool`.
*
* Slicing at the first `[` and discarding the rest would collapse every
* dimension at once. That is indistinguishable from the correct answer for a
* one-dimensional array and wrong for every other -- `flags[0][1]` would
* resolve to nothing, leaving `flags[0][1] / flags[1][2]` accepted as a
* divide by zero.
*
* Returns null for a subscript into something with no dimension left, which
* is a bit index or an error -- either way not a declared element type.
*/
private static elementType(typeName: string): string | null {
const open = typeName.indexOf("[");
if (open <= 0) return null;
const close = typeName.indexOf("]", open);
Iif (close < 0) return null;
return typeName.slice(0, open) + typeName.slice(close + 1);
}
/**
* Walk a chain from its base, applying one step at a time.
*
* `current` carries a type for a subscript or member step, and the callee's
* NAME for a call step -- a call is applied to what precedes it, which is a
* function name rather than a value. The name path is tracked alongside so a
* call can be resolved whether it is written bare (`isReady()`) or qualified
* (`Sensors.isReady()`).
*/
private applyChain(
base: string | null,
baseName: string,
steps: IChainStep[],
): string | null {
let current = base;
const nameParts = [baseName];
for (const step of steps) {
if (step.isCall) {
// Issue #1183 review: a bool-returning call was unresolvable, so
// `n / isReady()` passed and divided by zero at runtime.
//
// functionReturnTypes is keyed by transpiled C name, so the key is
// built with QualifiedCName -- the single encoder -- rather than
// re-derived by hand (CLAUDE.md).
return (
CodeGenState.getFunctionReturnType(
QualifiedCName.fromParts(nameParts),
) ?? null
);
}
if (step.member) {
nameParts.push(step.member);
}
if (!current) {
// No value type yet. A member step may still be building a callee name,
// so keep walking; anything else cannot be resolved.
Eif (step.member) continue;
return null;
}
if (step.isSubscript) {
current = OperandTypeResolver.elementType(current);
} else if (step.member) {
current = CodeGenState.getStructFieldType(current, step.member) ?? null;
} else E{
return null;
}
}
return current;
}
/**
* Declared type of an assignment target, following `postfixTargetOp` steps.
* `global.x` and a bare `x` resolve the same way; `this.x` resolves `x`
* against the enclosing scope frame, which is where a scope member is
* recorded.
*/
public typeOfAssignmentTarget(
ctx: Parser.AssignmentTargetContext,
frame: IScopeFrame,
): string | null {
const baseName = ctx.IDENTIFIER()?.getText();
Iif (!baseName) return null;
const steps: IChainStep[] = ctx.postfixTargetOp().map((op) => ({
member: op.DOT() !== null ? (op.IDENTIFIER()?.getText() ?? null) : null,
isSubscript: op.LBRACKET() !== null,
isCall: false, // an assignment target is never a call
}));
return this.applyChain(
this.scopes.typeOfName(baseName, frame),
baseName,
steps,
);
}
/**
* Declared type of a postfix expression operand. A call anywhere in the chain
* makes the type unresolvable here -- the result type is a function's, not a
* declaration's.
*/
public typeOfPostfixExpression(
ctx: Parser.PostfixExpressionContext,
frame: IScopeFrame,
): string | null {
const primary = ctx.primaryExpression();
Iif (!primary) return null;
// Copy: shifting the parser's own child array would corrupt the tree.
const ops = [...ctx.postfixOp()];
let base: string | null;
let baseName: string;
if (primary.THIS() ?? primary.GLOBAL()) {
// `this.member` / `global.member`: the first step names the declaration.
const firstMember = ops.shift()?.IDENTIFIER()?.getText();
Iif (!firstMember) return null;
// `this.member()` transpiles to a scope-qualified C name, so the callee
// key needs the enclosing scope. `global.` is deliberately not qualified.
baseName =
primary.THIS() !== null && frame.scope
? ScopeUtils.qualifyInScope(firstMember, frame.scope)
: firstMember;
base = this.scopes.typeOfName(firstMember, frame);
} else {
const identifier = primary.IDENTIFIER()?.getText();
Iif (!identifier) return null;
baseName = identifier;
base = this.scopes.typeOfName(identifier, frame);
}
const steps: IChainStep[] = [];
for (const op of ops) {
// Neither `.member` nor `[index]` is a call suffix.
const isSubscript = op.LBRACKET() !== null;
const member = op.DOT() !== null ? op.IDENTIFIER()?.getText() : null;
steps.push({
member: member ?? null,
isSubscript,
isCall: !isSubscript && !member,
});
}
return this.applyChain(base, baseName, steps);
}
/**
* Declared type of any expression node that resolves to a single value leaf,
* descending through pass-through operator levels and parentheses.
*
* Returns null for a multi-operand level: that is an operator application,
* whose own level reports on its own operands.
*/
public typeOfOperand(
ctx: ParserRuleContext,
frame: IScopeFrame,
): string | null {
let node: ParseTree = ctx;
while (node instanceof ParserRuleContext && node.getChildCount() === 1) {
const child = node.getChild(0);
Iif (!child) break;
node = child;
}
// A comparison or logical operator yields a Boolean whatever its operands
// were, so the expression HAS a type even though no declaration names it.
//
// Issue #1183 review: without this, `n / (a && b)` read as "type unknown"
// and passed. The parent arithmetic operator is the only place that
// violation can be reported -- `a && b` is well-formed on its own, so
// nothing reports at the child level.
if (OperandTypeResolver.isBooleanValuedOperator(node)) {
return BOOLEAN_TYPE_NAME;
}
if (node instanceof Parser.TernaryExpressionContext) {
return this.typeOfTernary(node, frame);
}
if (node instanceof Parser.UnaryExpressionContext) {
// `!x` is Boolean; `-x`, `~x` and `&x` are not.
return node.getChild(0)?.getText() === "!" ? BOOLEAN_TYPE_NAME : null;
}
if (node instanceof Parser.PostfixExpressionContext) {
return this.typeOfPostfixExpression(node, frame);
}
if (node instanceof Parser.PrimaryExpressionContext) {
const inner = node.expression();
return inner ? this.typeOfOperand(inner, frame) : null;
}
if (node instanceof ParserRuleContext) return null;
const text = node.getText();
if (text === "true" || text === "false") return BOOLEAN_TYPE_NAME;
return this.scopes.typeOfName(text, frame);
}
}
export default OperandTypeResolver;
|