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 | 99x 4x 4x 2x 2x 99x 8x 3x | /**
* NameExistence — one answer to "does this name denote anything visible HERE?"
*
* `TypeBinding` maps a type context to a C name; it never asks whether the name
* denotes a type, and its `null` means "no grammar alternative matched", not
* "not found". So every position that consumes a name fell back to emitting the
* raw source text: `CodeGenerator.getTypeName` via `resolved ?? ctx.getText()`
* (#1312) and `TypeValidator.resolveBareIdentifier` via a `null` that conflates
* "emit unchanged, it is fine" with "no idea what this is" (#1353). Both exited
* 0 and emitted C that the C compiler rejects.
*
* ## Why two sources of truth, and which one answers which question
*
* The two available symbol views disagree, and the disagreement IS #1312:
*
* | view | scope | sibling case says |
* | --------------------------------- | --------- | ----------------- |
* | `ICodeGenSymbols` `known*` sets | per FILE | `Mode` unknown |
* | `SymbolTable.getOverloadsByCName` | whole RUN | `Mode` known |
*
* `ICodeGenSymbols` is built by `_declareFile(tree, path, file.cnextIncludes)`
* (`Transpiler.ts:485`), so it holds exactly what this file can see. The
* `SymbolTable` accumulates every file in the run and is cleared once, so a
* sibling that was never included is still in it. Asking the run-wide table
* whether a C-Next type exists would answer "yes" in the file that cannot see
* it, and the diagnostic would never fire where it is needed -- the trap #1312
* names explicitly.
*
* So the two questions are routed to the view that can answer them:
*
* - a **C-Next** name is checked against the per-file sets, because C-Next
* visibility is what the include graph governs;
* - a **C/C++** name is checked against the run-wide table, because external
* types arrive through header includes that are already resolved per file,
* and because the failure directions are not symmetric. Missing an external
* type means a diagnostic does not fire (the status quo). Wrongly rejecting
* one means valid code stops compiling. Only the second is a regression, so
* the external side is deliberately permissive.
*
* `CodeGenState.isScopeType()` cannot serve here on either count: it reads the
* run-wide table, and it filters to `ESourceLanguage.CNext`, so it is wrong in
* both directions at once for this question.
*/
import ESourceLanguage from "../../../utils/types/ESourceLanguage";
import ICodeGenSymbols from "../../types/ICodeGenSymbols";
import SymbolTable from "./SymbolTable";
class NameExistence {
/**
* Whether a bare type name denotes a type this file can see.
*
* `CodeGenState.callbackTypes` is deliberately NOT consulted. It is codegen
* state -- filled by `CodeGenerator.registerCallbackType` and cleared at the
* start of `generate()`, both of which run after the analyzers -- so at
* analysis time it is empty for the first file and holds file N-1's function
* names for every file after. Reading it made E0426 order-dependent: the same
* two files with their `#include` lines swapped either diagnosed the undefined
* type or emitted C the compiler rejects at exit 0. `_isKnownCNextType` already asks
* `symbols.functionReturnTypes`, which is the per-file view of the same
* ADR-029 fact and is correct here.
*
* Only the bare `userType()` branch belongs here. `this.T`, `global.T` and
* `Scope.T` state their scope in the syntax and are resolved by their own
* branches; once a name is a string those answers are indistinguishable from
* a bare one, which is the same reason `TypeBinding` keeps them separate.
*/
static isKnownType(
typeName: string,
symbols: ICodeGenSymbols,
symbolTable: SymbolTable,
): boolean {
return (
NameExistence._isKnownCNextType(typeName, symbols) ||
NameExistence._isKnownForeignName(typeName, symbolTable)
);
}
/**
* ADR-017: an enum member may be written bare where the expected type makes
* it unambiguous -- a variable declaration, a struct field initializer, a
* switch case, a return statement, a ternary arm. It is a value, but it is
* neither a variable nor a type, so neither of the other lookups sees it.
*/
static isKnownEnumMember(name: string, symbols: ICodeGenSymbols): boolean {
for (const members of symbols.enumMembers.values()) {
if (members.has(name)) {
return true;
}
}
return false;
}
private static _isKnownCNextType(
typeName: string,
symbols: ICodeGenSymbols,
): boolean {
return (
symbols.knownEnums.has(typeName) ||
symbols.knownStructs.has(typeName) ||
symbols.knownBitmaps.has(typeName) ||
symbols.knownRegisters.has(typeName) ||
symbols.knownScopes.has(typeName) ||
symbols.opaqueTypes.has(typeName) ||
// ADR-029: a function definition creates a callback type, so every
// function name is also a type name. `CodeGenState.callbackTypes` is
// filled during codegen, which is after this runs, so the per-file
// function map is the view that has the answer at analysis time.
symbols.functionReturnTypes.has(typeName)
);
}
/**
* A name declared by an included C or C++ header. Checked against the
* run-wide table on purpose -- see the class comment on why the external side
* is permissive.
*/
private static _isKnownForeignName(
name: string,
symbolTable: SymbolTable,
): boolean {
return symbolTable
.getOverloadsByCName(name)
.some((symbol) => symbol.sourceLanguage !== ESourceLanguage.CNext);
}
}
export default NameExistence;
|