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 | 1560x 632x 928x 12x 916x 53x 863x 254x 254x 12x 242x 53x 189x 75x 114x 697x 697x 697x 545x 254x 254x 114x 140x 12x 140x 140x 291x 262x 262x 321x 98x 98x 98x 98x 98x 98x 88x 3x 1x | /**
* Resolves the qualified name of the function a postfix expression calls.
*
* "What function does this call target?" is one decision, and it is asked by
* more than one analyzer: FunctionCallAnalyzer (ADR-030 define-before-use) and
* ReturnValueUseAnalyzer (ADR-070 / E0708). It is deliberately owned here
* rather than implemented per-caller.
*
* This matters more than it looks. ADR-016 makes `this` and `global` their own
* tokens, and each qualifier resolves differently (`this.m` -> CurrentScope__m,
* `global.m` -> m, `Scope.m` -> Scope__m). Two implementations of that would
* agree only for as long as nobody touched either -- and a caller that resolved
* a name differently would silently enforce its rule on a different function
* than the one being called.
*
* Callers supply their own context (enclosing scope, known scope names) rather
* than this module reaching for global state, so the resolution is a pure
* function of what the caller can see.
*/
import * as Parser from "../../parser/grammar/CNextParser";
import QualifiedCName from "../../../../utils/QualifiedCName";
import ScopeUtils from "../../../../utils/ScopeUtils";
import ICalleeResolution from "../types/ICalleeResolution";
import type IScopeSymbol from "../../../types/symbols/IScopeSymbol";
class CalleeNameResolver {
/**
* The base name a postfix expression starts from: an identifier, or the
* ADR-016 qualifier keywords, which are separate tokens rather than
* identifiers. Returns null for anything that cannot start a named call.
*/
static baseName(primary: Parser.PrimaryExpressionContext): string | null {
if (primary.IDENTIFIER()) {
return primary.IDENTIFIER()!.getText();
}
if (primary.THIS()) {
return "this";
}
if (primary.GLOBAL()) {
return "global";
}
return null;
}
/**
* Resolve one member-access step. Returns the new qualified name, or null
* when the base is not something a C-Next call can be named against.
*/
static resolveMemberAccess(
resolvedName: string,
op: Parser.PostfixOpContext,
currentScope: IScopeSymbol | null,
isScope: (name: string) => boolean,
): string | null {
const memberName = op.IDENTIFIER()!.getText();
// this.member -> CurrentScope__member (only meaningful inside a scope)
if (resolvedName === "this") {
return currentScope
? ScopeUtils.qualifyInScope(memberName, currentScope)
: null;
}
// Issue #985: global.member -> member (strip the qualifier)
if (resolvedName === "global") {
return memberName;
}
if (isScope(resolvedName)) {
return QualifiedCName.fromParts([resolvedName, memberName]);
}
// Object.method or a chained access -- not a C-Next function call
return null;
}
/**
* Walk a postfix expression's operations to the call, building the callee's
* qualified name as it goes.
*/
static resolveCallTarget(
ops: Parser.PostfixOpContext[],
baseName: string,
currentScope: IScopeSymbol | null,
isScope: (name: string) => boolean,
): ICalleeResolution {
let resolvedName = baseName;
let isGlobalCall = baseName === "global";
for (const op of ops) {
if (op.IDENTIFIER()) {
const resolved = CalleeNameResolver.resolveMemberAccess(
resolvedName,
op,
currentScope,
isScope,
);
if (resolved === null) {
return { resolvedName, foundCall: false, isGlobalCall };
}
// Resolution through a known scope makes this a scope method call,
// not a global function lookup.
if (isGlobalCall && isScope(resolvedName)) {
isGlobalCall = false;
}
resolvedName = resolved;
continue;
}
if (op.argumentList() || op.getChildCount() === 2) {
Eif (op.getText().startsWith("(")) {
return { resolvedName, foundCall: true, isGlobalCall };
}
}
}
return { resolvedName, foundCall: false, isGlobalCall };
}
/**
* Full resolution for a postfix expression, keeping `isGlobalCall`.
*
* Callers that look a name up need that flag: an unqualified name inside a
* scope may mean the scope's member (ADR-057), but an explicitly
* `global.`-qualified one never does. Dropping it would make the two
* indistinguishable at the lookup.
*/
static resolveDetailed(
postfix: Parser.PostfixExpressionContext,
currentScope: IScopeSymbol | null,
isScope: (name: string) => boolean,
): { name: string; isGlobalCall: boolean } | null {
const base = CalleeNameResolver.baseName(postfix.primaryExpression());
Iif (base === null) return null;
const result = CalleeNameResolver.resolveCallTarget(
postfix.postfixOp(),
base,
currentScope,
isScope,
);
Iif (!result.foundCall) return null;
Iif (result.resolvedName === "this" || result.resolvedName === "global") {
return null;
}
return { name: result.resolvedName, isGlobalCall: result.isGlobalCall };
}
/**
* The callee's qualified name, or null when this is not a named call.
*/
static resolve(
postfix: Parser.PostfixExpressionContext,
currentScope: IScopeSymbol | null,
isScope: (name: string) => boolean,
): string | null {
return (
CalleeNameResolver.resolveDetailed(postfix, currentScope, isScope)
?.name ?? null
);
}
/**
* ADR-057: inside a scope, a bare `read()` may mean `this.read()`. The name
* a caller should retry its lookup against, or null when the fallback does
* not apply.
*
* This is the *decision* -- "when does an unqualified name mean a scope
* member" -- and it is shared deliberately. Each caller then applies it to
* its own index (defined functions, return types), but none of them re-derives
* when the fallback is allowed. `global.`-qualified calls are excluded because
* `global.` explicitly means the global scope.
*/
static scopeQualifiedCandidate(
name: string,
currentScope: IScopeSymbol | null,
isGlobalCall: boolean,
): string | null {
if (!currentScope || isGlobalCall) return null;
// Already qualified -- there is nothing to fall back from.
if (QualifiedCName.isQualified(name)) return null;
return ScopeUtils.qualifyInScope(name, currentScope);
}
}
export default CalleeNameResolver;
|