Skip to content
Merged
28 changes: 28 additions & 0 deletions docs/calls_out_rule_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,34 @@ Summary: 37 of 65 languages agree outside the global **B**. The rest are **K** (
**D** (8), **S** (1: tcl), **G** (1: cobol), **I** (1: matlab) and **R** (1: haskell). Every
disagreement is precision (a non-call emitted), except **B**, which is recall.

## #3359 resolution (classes K, A, S)

Fixed by #3359, re-censused on the same crucible plus keyword-rosetta: 4,300+ non-call names
left `calls_out_to` across 25 languages, and every one was checked to be a keyword, special
form, annotation or string word (no real callee dropped).

- **K:** keyword entries added to each language's `_calls_out_ignore` (built-ins and
`_CALLS_OUT_GLOBAL_IGNORE` untouched; #3361 owns those). The per-language set is now compared
**exactly** in a case-sensitive language and casefolded only for `identifier_case:
insensitive`, so a keyword never swallows a capitalised callee (go `v.Type()`, C#
`factory.This()`, perl `$self->Warn(`).
- **A:** `CALLS_OUT_C_STYLE_NO_ANNOTATION` (`(?<!@)`) for java, kotlin, swift, dart, groovy and
scala. Python/TypeScript/JavaScript keep the plain pattern: `@retry(3)` is a call.
- **S:** tcl's command-position rule skips upper-case SQL keywords that start the lines of a
brace-quoted query (upper-case only: `set`/`update` are real Tcl commands).
- **cobol:** `END-PERFORM`/`END-CALL` no longer hand the next statement's first word to the
verb (`(?<![\w-])`), which also recovered a real `PERFORM` target; inline
`PERFORM VARYING/UNTIL/WITH TEST` names no paragraph. **agc:** `TC Q` (the return) is ignored.

Left open (a regex change too invasive for a keyword list, or not a keyword after all):
scheme `let` binding lists `((x 1))` (the same `((` shape is a `cond` clause or a curried call);
dotted and use-site annotations (`@a.b.C(`, `@file:JvmName(`); rust `Fn(` trait sugar and
`#[cfg(not(...))]` predicates; tcl brace-quoted *prose* (lower-case words at line start);
powershell hashtable-key and enum-member lines in command position; cobol
`PERFORM <data-name> TIMES`. Not keywords on inspection, so kept as calls: perl `do`/`then`
(every crucible hit is `$dbh->do(` / `->then(`), php `match` (`$this->match(`), tcl `default`
(a macports command), perl `qx(` (runs a shell command).

## How the census was taken

A scratch probe (not committed; same shape as `tests/tools/wrapper_probe.py`) ran the
Expand Down
27 changes: 21 additions & 6 deletions gitgalaxy/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
COMPILED_HANDSHAKE_REGISTRY,
HTML_NONEXECUTABLE_SCRIPT_TAG,
)
from gitgalaxy.standards.language_standards._shared_patterns import CALLS_OUT_C_STYLE
from gitgalaxy.standards.language_standards._shared_patterns import (
CALLS_OUT_C_STYLE,
CALLS_OUT_C_STYLE_NO_ANNOTATION,
)

HAS_TIKTOKEN = False
try:
Expand Down Expand Up @@ -879,7 +882,8 @@ def _cpp_class_has_body(code_stream: str, scan_start: int) -> bool:
# cannot distinguish from user calls. Case-sensitive on purpose: languages
# whose keywords are case-insensitive (fortran, abap, pli, rexx, db2_sql, ada)
# declare their own lowercase words via the per-language `_calls_out_ignore`
# rule, which is compared casefolded at the filter site.
# rule, which is compared casefolded at the filter site for a language declared
# `identifier_case: insensitive`, and exactly for every other language (#3359).
# #3329: member-access separators a call's qualifier is joined by -- `a.b()`,
# `p->f()`, `Ns::f()`, `a?.b()`. Longest first, so `->`/`::`/`?.` win over `.`.
_QUALIFIER_SEPARATORS = ("->", "::", "?.", ".")
Expand Down Expand Up @@ -8922,10 +8926,12 @@ def _calculate_block_metrics(
if invocation_pattern:
# Apply literal shield to avoid capturing words inside strings
safe_block = self._apply_literal_shield(block, self.primary_lang_id)
if invocation_pattern is CALLS_OUT_C_STYLE:
if invocation_pattern is CALLS_OUT_C_STYLE or invocation_pattern is CALLS_OUT_C_STYLE_NO_ANNOTATION:
# #3360 (C5): note which callees were captured only on a nested
# func_start header (`def inner(`). _function_slice drops them
# once it knows the slicer really emitted that nested unit.
# #3359: the annotation-free variant (java/kotlin/swift/dart/
# groovy/scala) gets the same check.
decl_headers = _declaration_headers(rules.get("func_start"), safe_block)
for m in invocation_pattern.finditer(safe_block):
callee = m.group(1)
Expand All @@ -8942,9 +8948,16 @@ def _calculate_block_metrics(
raw_calls = invocation_pattern.findall(safe_block)

# Per-language additions to the global ignore set (Epic #3264 Phase 3).
# Authored lowercase in the profile and compared casefolded, so
# case-insensitive languages filter their keywords in any spelling.
# Authored lowercase in the profile. A case-insensitive language
# (`identifier_case: insensitive`) compares casefolded, so it filters its
# keywords in any spelling; #3359: a case-sensitive one compares exactly,
# so a keyword never swallows a real callee spelled with capitals (go's
# `func` keyword vs a `Func()` method, rust's `let` vs an `Expr::Let(`
# variant, C#'s `this(` chaining vs a `factory.This()` call).
lang_ignore = rules.get("_calls_out_ignore") or frozenset()
_fold_ignore = (
self.languages.get(self.primary_lang_id, {}).get("identifier_case") == IDENTIFIER_CASE_INSENSITIVE
)
# Deduplicate and filter (excluding the function calling itself recursively).
# #3292: a `positional` language's units cannot be invoked by name, so a
# callee spelled like the unit is never recursion -- `//IEFBR14 EXEC
Expand All @@ -8959,7 +8972,9 @@ def _calculate_block_metrics(
dict.fromkeys(
c
for c in raw_calls
if c not in _CALLS_OUT_GLOBAL_IGNORE and c.casefold() not in lang_ignore and c != self_name
if c not in _CALLS_OUT_GLOBAL_IGNORE
and (c.casefold() if _fold_ignore else c) not in lang_ignore
and c != self_name
)
)

Expand Down
9 changes: 9 additions & 0 deletions gitgalaxy/standards/language_standards/_shared_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@
# C-Family / Algol-Family (name followed by optional space and open parenthesis)
CALLS_OUT_C_STYLE = re.compile(r"\b([a-zA-Z_]\w*)\s*\(")

# #3359 (contract C1): the C-style pattern for languages whose `@Name(...)` is a
# metadata annotation -- a declaration, never an invocation (java, kotlin, swift,
# dart, groovy, scala). NOT for python/typescript/javascript, where a decorator
# factory `@retry(3)` really is a call to `retry`. A dotted annotation
# (`@javax.annotation.Generated(`) still leaks its last segment, a known gap:
# the lookbehind is fixed-width on purpose (Rule 5). detector.py treats this
# pattern exactly like CALLS_OUT_C_STYLE (qualifier capture included).
CALLS_OUT_C_STYLE_NO_ANNOTATION = re.compile(r"(?<!@)\b([a-zA-Z_]\w*)\s*\(")

# Unsupported / AST-Required (Shell, Markup, Data, Config)
# Mapped to None to officially declare intentional blindness rather than extracting garbage.
CALLS_OUT_UNSUPPORTED = None
Expand Down
12 changes: 12 additions & 0 deletions gitgalaxy/standards/language_standards/languages/ada.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@
"rem",
"others",
"aliased",
# #3359 (contract C2): keywords and special forms, never calls
"is",
"else",
"elsif",
"then",
"not",
"and",
"or",
"xor",
"in",
"when",
"with",
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": re.compile(r"(?m)\bTC\s+([A-Z][A-Z0-9]*)"),
# #3359 (contract C4): `TC Q` transfers control through the Q register --
# the return idiom, not a call. Upper-case: agc is not declared
# `identifier_case: insensitive`, so this set is compared exactly.
"_calls_out_ignore": frozenset({"Q"}),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch (Control Flow / Branching)
# Decisions only. EXCLUDES fatal alarms (bailout_hits) and, since #2764,
Expand Down
14 changes: 13 additions & 1 deletion gitgalaxy/standards/language_standards/languages/cobol.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,19 @@
# (docs/calls_out_rule_contract.md C4) -- and is recorded beside it as a
# transfer (`_transfers_out` below), so the paragraphs reached only by
# GO TO (a quarter of the crucible's) stay reachable.
"calls_out": re.compile(r"(?i)\b(?:PERFORM|CALL)\s+['\"]?([A-Za-z0-9_-]+)['\"]?"),
# #3359: `(?<![\w-])`, not `\b` -- the scope terminators `END-PERFORM` /
# `END-CALL` end in the verb, so `\b` let the NEXT statement's first word
# (`END-PERFORM` newline `MOVE ...`) be captured as a callee.
"calls_out": re.compile(r"(?i)(?<![\w-])(?:PERFORM|CALL)\s+['\"]?([A-Za-z0-9_-]+)['\"]?"),
# #3359 (contract C2): the inline PERFORM forms (`PERFORM VARYING ...`,
# `PERFORM UNTIL ...`, `PERFORM WITH TEST ...`) name no paragraph.
"_calls_out_ignore": frozenset(
{
"varying",
"until",
"with",
}
),
# #3362: GO TO <paragraph|section> -- an unconditional transfer of control.
# First target only for `GO TO A B C DEPENDING ON X` (one occurrence on
# the whole crucible). WHENEVER ... GO TO (embedded SQL) is excluded: that
Expand Down
15 changes: 15 additions & 0 deletions gitgalaxy/standards/language_standards/languages/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"static_assert",
"operator",
"constexpr",
"alignas",
"noexcept",
"requires",
"typeid",
"co_await",
"co_yield",
"co_return",
}
),
# 1. branch (Control Flow / Branching)
# Control flow jumps. Includes modern coroutine jumps (co_yield, co_await).
# EXCLUDES exceptions (bailout_hits).
Expand Down
29 changes: 29 additions & 0 deletions gitgalaxy/standards/language_standards/languages/csharp.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,35 @@
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"foreach",
"nameof",
"default",
"var",
"is",
"not",
"or",
"and",
"when",
"lock",
"this",
"base",
"new",
"static",
"internal",
"private",
"protected",
"public",
"in",
"out",
"ref",
"fixed",
"checked",
"unchecked",
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch (Control Flow / Branching)
# Decisions and logical jumps. EXCLUDES throw (bailout_hits).
Expand Down
13 changes: 11 additions & 2 deletions gitgalaxy/standards/language_standards/languages/dart.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import re
from typing import Any

from .._shared_patterns import CALLS_OUT_C_STYLE, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT
from .._shared_patterns import CALLS_OUT_C_STYLE_NO_ANNOTATION, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT

DEFINITION: dict[str, Any] = {
"_meta": {
Expand Down Expand Up @@ -40,7 +40,16 @@
"lexical_family": "standard_block",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
"calls_out": CALLS_OUT_C_STYLE_NO_ANNOTATION, # #3359: `@Name(` is an annotation (C1)
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"when",
"final",
"var",
"Function", # the function-TYPE keyword: `void Function(int)`
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch: decisions that split flow. Includes modern pattern guards (when) and null-coalescing.
"branch": re.compile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"in",
"not",
"and",
"or",
"elif",
"is",
"yield",
"lambda",
"del",
"with",
"raise",
"from",
"as",
"else",
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch (Control Flow / Branching)
# Decisions and logical jumps. EXCLUDES raise (bailout_hits).
Expand Down
9 changes: 9 additions & 0 deletions gitgalaxy/standards/language_standards/languages/go.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"func",
"var",
"const",
"type",
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch (Control Flow / Branching)
# Includes select/case and range-based loops. EXCLUDES panic (bailout_hits).
Expand Down
11 changes: 9 additions & 2 deletions gitgalaxy/standards/language_standards/languages/groovy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import re
from typing import Any

from .._shared_patterns import CALLS_OUT_C_STYLE, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT
from .._shared_patterns import CALLS_OUT_C_STYLE_NO_ANNOTATION, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT

DEFINITION: dict[str, Any] = {
"_meta": {
Expand All @@ -38,7 +38,14 @@
"lexical_family": "standard_block",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
"calls_out": CALLS_OUT_C_STYLE_NO_ANNOTATION, # #3359: `@Name(` is an annotation (C1)
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"this",
"in",
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch (Control Flow / Branching)
"branch": re.compile(r"\b(if|else|switch|case|default|for|while|in)\b|\?"),
Expand Down
11 changes: 9 additions & 2 deletions gitgalaxy/standards/language_standards/languages/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import re
from typing import Any

from .._shared_patterns import CALLS_OUT_C_STYLE, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT
from .._shared_patterns import CALLS_OUT_C_STYLE_NO_ANNOTATION, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT

DEFINITION: dict[str, Any] = {
"_meta": {
Expand Down Expand Up @@ -45,7 +45,14 @@
"lexical_family": "standard_block",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
"calls_out": CALLS_OUT_C_STYLE_NO_ANNOTATION, # #3359: `@Name(` is an annotation (C1)
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"this",
"synchronized",
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch (Control Flow / Branching)
# Includes modern switch expressions (yield) and pattern guards (when).
Expand Down
13 changes: 11 additions & 2 deletions gitgalaxy/standards/language_standards/languages/kotlin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import re
from typing import Any

from .._shared_patterns import CALLS_OUT_C_STYLE, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT
from .._shared_patterns import CALLS_OUT_C_STYLE_NO_ANNOTATION, GLOBAL_FRAGILE_DEBT, GLOBAL_PLANNED_DEBT

DEFINITION: dict[str, Any] = {
"_meta": {
Expand Down Expand Up @@ -39,7 +39,16 @@
"lexical_family": "standard_block",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
"calls_out": CALLS_OUT_C_STYLE_NO_ANNOTATION, # #3359: `@Name(` is an annotation (C1)
# #3359 (contract C2): keywords and special forms, never calls
"_calls_out_ignore": frozenset(
{
"when",
"this",
"val",
"var",
}
),
# --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE ---
# 1. branch (Control Flow / Branching)
# Decisions and logical jumps. Includes modern 'when' and Elvis operator.
Expand Down
Loading
Loading