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 303 304 305 306 307 | 480x 480x 480x 480x 480x 480x 480x 480x 480x 480x 480x 3x 3x 3x 3x 3x 477x 477x 477x 480x 3x 474x 27x 127x 127x 127x 604x 580x 580x 40x 6x 6x 580x 133x 133x 133x 133x 133x 133x 133x 1x 1x 1x 132x 133x 1x 27x 30x 27x 27x 5x 5x 5x 2x 2x 22x 1x 1x 21x 24x 24x 21x 27x 24x 24x 24x 19x 4x 4x 4x 20x 3x 6x 6x 6x 33x 33x 499x 499x 494x 494x 5x 7x 18x 18x 480x 480x 480x 480x | /**
* ADR-035 array initializers and ADR-036 declaration shape:
* E0866, E0874, E0875, E0876, E0892.
*
* #1322. Five throws in `output/` -- `VariableDeclHelper` for a C-style
* declaration, two in `CodeGenerator` for a C-style and an unbounded
* parameter, two in `ArrayInitHelper` for the fill-all form with an inferred
* size and for an initializer of the wrong length -- plus the string array
* arm of E0866, which `StringDeclarationAnalyzer` carried because that
* family moved first. Every fact is in the parse tree: where the brackets
* are, whether a dimension has an expression, how many elements a list has.
*
* ## One count, every element type
*
* "The initializer has the declared number of elements" was decided twice:
* for string arrays here in 2.1 (E0866) and for everything else in codegen,
* which counted the elements it had just generated. One rule now, with the
* declared dimensions read off the type and the elements counted off the
* list -- at EVERY level. Codegen counted the outer list only, so
* `u8[2][2] m <- [[1, 2, 3], [4, 5]]` was accepted and reached C as an
* excess-elements initializer.
*
* ## Two holes closed, probed
*
* - `u8[2] b <- a` (an array initialized from another array) emitted
* `uint8_t b[2] = a;`, which C rejects. The string analyzer already said
* "must be initialized by a list" for a string array; it is said for every
* array now. A string LITERAL into a `u8` array is the exception ADR-035
* makes (`u8[] message <- "Hello"`).
* - The nested count above.
*
* ## Reproduced, not closed, stated
*
* Codegen rejected the C-style form (`u8 arr[4]`) for a variable declaration
* and a parameter, and accepted it for a scope member, a struct member and a
* `for` header's declaration -- thirty-one fixtures use it in a scope or a
* struct, and a unit test pins the scope case as intended. ADR-036 names the
* prefix form as the only one; this pass rejects exactly where codegen did
* and records the rest for the ADR's owner, since closing it is a language
* change on code the corpus treats as valid.
*/
import { ParserRuleContext, ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import TypeText from "./helpers/TypeText";
import ParserUtils from "../../utils/ParserUtils";
import IArrayDeclarationError from "./types/IArrayDeclarationError";
import ConstantExpression from "./helpers/ConstantExpression";
import type IAnalysisContext from "./types/IAnalysisContext";
/** A declared dimension: its size when it can be known here, else null. */
type TDimension = number | null;
class ArrayDeclarationListener extends CNextListener {
private readonly found: IArrayDeclarationError[] = [];
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
constructor(private readonly context: IAnalysisContext) {
super();
}
public errors(): IArrayDeclarationError[] {
return this.found;
}
override enterVariableDeclaration = (
ctx: Parser.VariableDeclarationContext,
): void => {
// A scope member reaches its own generator, which never rejected the
// C-style form -- reproduced, see the class comment.
const isScopeMember = ctx.parent instanceof Parser.ScopeMemberContext;
const trailing = ctx.arrayDimension();
const identifier = ctx.IDENTIFIER();
const typeCtx = ctx.type();
Iif (!identifier || !typeCtx) return; // the constructor-call form
if (trailing.length > 0 && !isScopeMember) {
const base = TypeText.withoutDimensions(typeCtx.getText());
const existing = ArrayDeclarationListener.dimensionText(
typeCtx.arrayType()?.arrayTypeDimension() ?? [],
);
const after = ArrayDeclarationListener.dimensionText(trailing);
this.report(
ctx,
"E0874",
`C-style array declaration is not allowed. Use '${base}${existing}${after} ${identifier.getText()}' instead of '${base}${existing} ${identifier.getText()}${after}'`,
"C-Next puts every array dimension in the type, before the name (ADR-036).",
);
return;
}
const arrayType = typeCtx.arrayType();
const expression = ctx.expression();
const dims = [...(arrayType?.arrayTypeDimension() ?? []), ...trailing];
if (this.checkEmptyDimensions(dims, identifier.getText(), !!expression)) {
return;
}
if (!arrayType || !expression) return;
this.checkInitializer(arrayType, expression);
};
override enterStructMember = (ctx: Parser.StructMemberContext): void => {
const dims = [
...(ctx.type().arrayType()?.arrayTypeDimension() ?? []),
...ctx.arrayDimension(),
];
this.checkEmptyDimensions(dims, ctx.IDENTIFIER().getText(), false);
};
/**
* #1822 (ADR-035): every dimension states its size, except the single
* dimension of a one-dimensional variable initialized by a list -- or, for
* `u8`, a string literal -- whose size is the number of elements. Which
* initializer is accepted is E0866's; this asks only whether there is one
* to count. Returns whether anything was reported.
*/
private checkEmptyDimensions(
dims: readonly (
| Parser.ArrayDimensionContext
| Parser.ArrayTypeDimensionContext
)[],
name: string,
hasInitializer: boolean,
): boolean {
if (dims.length === 1 && hasInitializer) return false;
let reported = false;
dims.forEach((dim, index) => {
if (dim.expression() !== null) return;
this.report(
dim,
"E0892",
`Array '${name}' leaves dimension ${index + 1} without a size`,
"Write the size. Only a one-dimensional array initialized by a list (or, for u8, a string literal) may leave it out, and its size is the number of elements (ADR-035).",
);
reported = true;
});
return reported;
}
override enterParameter = (ctx: Parser.ParameterContext): void => {
// `main(string args[])` is the language's own form for the command-line
// args (ADR-030), lowered to `argv` -- the one trailing `[]` that is not
// C-style. The same criterion codegen lowers it with, asked once.
const fn = ctx.parent?.parent;
Iif (
fn instanceof Parser.FunctionDeclarationContext &&
ParserUtils.isMainFunctionWithArgs(
fn.IDENTIFIER().getText(),
fn.parameterList(),
)
) {
return;
}
const trailing = ctx.arrayDimension();
const name = ctx.IDENTIFIER().getText();
const typeText = ctx.type().getText();
if (trailing.length > 0) {
const after = ArrayDeclarationListener.dimensionText(trailing);
this.report(
ctx,
"E0874",
`C-style array parameter is not allowed. Use '${typeText}${after} ${name}' instead of '${typeText} ${name}${after}'`,
"C-Next puts every array dimension in the type, before the name (ADR-036).",
);
return;
}
const dims = ctx.type().arrayType()?.arrayTypeDimension() ?? [];
if (dims.some((d) => d.expression() === null)) {
this.report(
ctx,
"E0875",
"Unbounded array parameters are not allowed. All dimensions must have explicit sizes for memory safety.",
"A parameter's dimensions are what the callee can trust; write the size, e.g. `u8[8] data` (ADR-036).",
);
}
};
/**
* ADR-035: an array's initializer is a list with one element per slot at
* every level, or the fill-all form `[v*]` for a level, or -- for a `u8`
* array -- a string literal. An inferred size (`u8[]`) is fixed by the list,
* so nothing to count there; the fill-all form cannot fix one.
*/
private checkInitializer(
arrayType: Parser.ArrayTypeContext,
expression: Parser.ExpressionContext,
): void {
const dimensions = arrayType.arrayTypeDimension();
const inferred = dimensions.some((d) => d.expression() === null);
const initializer = ArrayDeclarationListener.arrayInitializerOf(expression);
if (initializer === null) {
const isStringLiteral = /^".*"$/.test(expression.getText().trim());
const isByteArray = arrayType.primitiveType()?.getText() === "u8";
if (isStringLiteral && isByteArray) return; // `u8[] s <- "Hello"` (ADR-035)
this.report(
expression,
"E0866",
`An array must be initialized by a list, not '${expression.getText()}'`,
"Write the elements out in brackets, or declare it empty and assign the elements afterwards (ADR-035).",
);
return;
}
if (inferred && initializer.STAR() !== null) {
this.report(
expression,
"E0876",
`Fill-all syntax ${expression.getText()} requires explicit array size`,
"An inferred size comes from counting the elements, and the fill-all form has none to count; write the dimension (ADR-035).",
);
return;
}
const sizes: TDimension[] = dimensions.map((d) => {
const expr = d.expression();
return expr === null
? null
: ConstantExpression.valueAt(expr, this.context);
});
this.checkLevel(initializer, sizes, 0);
}
/** Count one level of a nested list against its dimension, then descend. */
private checkLevel(
initializer: Parser.ArrayInitializerContext,
sizes: readonly TDimension[],
level: number,
): void {
if (initializer.STAR() !== null) return; // fill-all covers this level
const elements = initializer.arrayInitializerElement();
const declared = sizes[level];
if (declared !== null && declared !== undefined) {
if (elements.length !== declared) {
const where = level > 0 ? ` at nesting level ${level + 1}` : "";
this.report(
initializer,
"E0866",
`Array size mismatch: declared [${declared}] but the initializer has ${elements.length} element(s)${where}`,
"Give one element per slot, or use the fill-all form such as [0*] (ADR-035).",
);
return;
}
}
if (level + 1 >= sizes.length) return;
for (const element of elements) {
// A nested list parses as an `expression` element (the first
// alternative that fits), so it is found by descending, not by asking
// for the `arrayInitializer` alternative.
const expression = element.expression();
const nested =
element.arrayInitializer() ??
(expression === null
? null
: ArrayDeclarationListener.arrayInitializerOf(expression));
Eif (nested) this.checkLevel(nested, sizes, level + 1);
}
}
/** The `[...]` an expression IS, descending through single-child levels. */
private static arrayInitializerOf(
expression: Parser.ExpressionContext,
): Parser.ArrayInitializerContext | null {
let node: ParserRuleContext = expression;
while (node.getChildCount() === 1) {
const child = node.getChild(0);
if (!(child instanceof ParserRuleContext)) break;
node = child;
if (node instanceof Parser.ArrayInitializerContext) return node;
}
return null;
}
private static dimensionText(
dims: readonly (
| Parser.ArrayDimensionContext
| Parser.ArrayTypeDimensionContext
)[],
): string {
return dims.map((d) => `[${d.expression()?.getText() ?? ""}]`).join("");
}
private report(
at: ParserRuleContext,
code: string,
message: string,
helpText: string,
): void {
const { line, column } = ParserUtils.getPosition(at);
this.found.push({ code, line, column, message, helpText });
}
}
class ArrayDeclarationAnalyzer {
/** #1456: handed in rather than read off shared state. */
constructor(private readonly context: IAnalysisContext) {}
public analyze(tree: Parser.ProgramContext): IArrayDeclarationError[] {
const listener = new ArrayDeclarationListener(this.context);
ParseTreeWalker.DEFAULT.walk(listener, tree);
return listener.errors();
}
}
export default ArrayDeclarationAnalyzer;
|