Skip to content

Commit ae26a80

Browse files
author
mforce
committed
fix(#417): codex round 101 — cast-shaped wrappers in chain gaps
A cast between concatenated fragments — "post" + (string)"gres" — is a value-preserving wrapper the chain gap did not admit: the type name's identifier characters failed the character-class gap regex, the fold broke, and each fragment was harmless alone. The regex gap matcher is replaced by a structural checker that reuses TryConsumeTypeParen: before the plus, trivia and closing parens of a wrapped previous fragment; after it, trivia, grouping parens, unary operators, and cast-shaped parens. A bare identifier in the gap still breaks the fold (runtime boundary), and the checker stays conservative in the same direction as before — matching gap shapes that span some non-constant expressions can only add refusals, never open a hole. Verified with five positive forms — codex's exact cast gap, a nested-wrapper variant, a comment-plus-cast gap, and the plain and parenthesized gaps from rounds 79/82 as regressions — all failing by name, and a string.Concat call in the gap (identifier-led, runtime) passing. Restored, rebuilt, all three SchemaDocsTests green.
1 parent 762a0d6 commit ae26a80

1 file changed

Lines changed: 34 additions & 12 deletions

File tree

tests/Cluckwork.Api.IntegrationTests/SchemaDocsTests.cs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -448,21 +448,43 @@ public void PostgresImagePin_IsOneIdenticalStringAcrossEveryTrackedFile()
448448
tokens.Add((m.Index, m.Index + m.Length, m.Groups["body"].Value.Trim(), false));
449449
}
450450
tokens.Sort((a, b) => a.Start.CompareTo(b.Start));
451-
// Comments were blanked to spaces above, so a comment
452-
// around the plus is already plain whitespace here. The gap
453-
// matcher admits parentheses besides whitespace: parens are
454-
// semantically transparent in a constant string
455-
// concatenation, so a wrapped fragment still folds. This is
456-
// DELIBERATELY conservative — a gap like `) + (` also spans
457-
// some non-constant expressions (a call result concatenated
458-
// with a literal), and folding those can only ADD a refusal,
459-
// never open a hole; any identifier character in the gap
460-
// still breaks the fold.
461-
var plusGap = new Regex(@"^[\s()]*\+[\s()]*$");
451+
// The gap between chained tokens is `+` surrounded by
452+
// value-preserving wrappers: trivia (comments were blanked
453+
// to spaces above, but trivia is skipped regardless),
454+
// closing parens of a wrapped previous fragment before the
455+
// plus, and — after the plus — grouping parens, unary
456+
// operators, and CAST-shaped parens (the same structural
457+
// recognizer the interpolation scanner uses), so
458+
// `+ (string)` before the next fragment still folds. This
459+
// stays DELIBERATELY conservative: some non-constant
460+
// expressions match the same gap shape (a call result
461+
// concatenated with a literal), and folding those can only
462+
// ADD a refusal, never open a hole; a bare identifier in
463+
// the gap still breaks the fold.
464+
static bool IsPlusGap(string t, int start, int end)
465+
{
466+
var p = SkipTrivia(t, start);
467+
while (p < end && t[p] == ')') p = SkipTrivia(t, p + 1);
468+
if (p >= end || t[p] != '+') return false;
469+
p = SkipTrivia(t, p + 1);
470+
while (p < end)
471+
{
472+
if (t[p] == '(')
473+
{
474+
var castEnd = TryConsumeTypeParen(t, p);
475+
if (castEnd > 0 && castEnd <= end) { p = SkipTrivia(t, castEnd); continue; }
476+
p = SkipTrivia(t, p + 1);
477+
continue;
478+
}
479+
if (t[p] is '-' or '+' or '~' or '!') { p = SkipTrivia(t, p + 1); continue; }
480+
return false;
481+
}
482+
return true;
483+
}
462484
for (var i = 0; i < tokens.Count;)
463485
{
464486
var j = i;
465-
while (j + 1 < tokens.Count && plusGap.IsMatch(codeText[tokens[j].End..tokens[j + 1].Start]))
487+
while (j + 1 < tokens.Count && IsPlusGap(codeText, tokens[j].End, tokens[j + 1].Start))
466488
j++;
467489
var folded = string.Concat(tokens.Skip(i).Take(j - i + 1).Select(t => t.Value));
468490
var isChain = j > i;

0 commit comments

Comments
 (0)