You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Issue body is in the file /tmp/issue-body.txt — read it before proceeding.
101
+
The issue body (including all comments) has been saved to /tmp/issue-body.txt.
102
102
103
-
## Step 1: Write a regression test
103
+
## IMPORTANT — read before you start
104
104
105
-
Read .claude/skills/regression-test/SKILL.md for detailed guidance on writing regression tests for PHPStan bugs.
105
+
- **Read CLAUDE.md first.** It contains the codebase architecture, common bug-fix patterns, and conventions you must follow (e.g. never use \`instanceof\` to check type properties — use \`isSuperTypeOf()\` / \`isString()->yes()\` instead; prefer adding methods to the \`Type\` interface over scattered \`instanceof\` checks).
106
+
- **Think in parallel cases, not in isolation.** This is the most important instruction. A PHPStan bug report almost always describes *one* instance of a more general problem. Before and after implementing the fix, ask: *which other PHP constructs, type classes, or rules are structurally analogous and therefore likely to have the same bug?* Then write failing tests for those cases too and fix them with the same approach. Step 4 below tells you exactly how to do this — do not skip it.
107
+
- **Do not create new markdown/docs files.** No summaries, no notes, no README updates.
108
+
- **Do not create a branch, push, or create a PR** — that is handled automatically after you finish.
109
+
- **Use TodoWrite** to track your progress through the steps below. Update tasks as you go, and add new todos when you discover analogous cases worth fixing.
106
110
107
-
The issue body is already provided above — start from Step 2 of the skill (deciding test type). For Step 1 (gathering context), you only need to fetch the playground samples from any playground links found in the issue body.
111
+
## Step 1: Gather context and classify the bug
108
112
109
-
Skip Steps 5-6 of the skill (reverting fix and committing) — those are not needed here.
113
+
1. Read \`/tmp/issue-body.txt\` carefully, including all comments.
114
+
2. Fetch any PHPStan playground samples linked in the issue body (the sample code is the ground truth for the reproduction).
115
+
3. Identify which subsystem the bug lives in. Common areas:
116
+
- \`src/Analyser/NodeScopeResolver.php\` — AST traversal, control flow, variable assignments
117
+
- \`src/Analyser/MutatingScope.php\` — type tracking, scope state
118
+
- \`src/Analyser/TypeSpecifier.php\` — type narrowing from conditions (\`instanceof\`, \`is_*()\`, \`===\`, etc.)
119
+
- \`src/Type/\` — type system implementations (\`Type\` interface and its implementations, \`TypeCombinator\`)
120
+
- \`src/Rules/\` — individual analysis rules, organized by category
- \`src/PhpDoc/\` — PHPDoc parsing and type node resolution
123
+
4. Read the relevant files to understand the current behavior before changing anything. Form a hypothesis for the root cause before writing code.
110
124
111
-
The regression test should fail without the fix — verify this by running it before implementing the fix.
125
+
## Step 2: Write a regression test
112
126
113
-
## Step 2: Fix the bug
127
+
Read \`.claude/skills/regression-test/SKILL.md\` for detailed guidance on writing regression tests for PHPStan bugs.
114
128
115
-
Implement the fix in the source code under src/. Common areas to look:
116
-
- src/Analyser/NodeScopeResolver.php - AST traversal and scope management
117
-
- src/Analyser/MutatingScope.php - Type tracking
118
-
- src/Analyser/TypeSpecifier.php - Type narrowing from conditions
119
-
- src/Type/ - Type system implementations
120
-
- src/Rules/ - Rule implementations
121
-
- src/Reflection/ - Reflection layer
129
+
Since the issue body is already provided, start from Step 2 of the skill (deciding test type). For Step 1 (gathering context) you only need the playground samples you already fetched. Skip Steps 5–6 (reverting fix and committing) — they are not needed here.
122
130
123
-
Read CLAUDE.md for important guidelines about the codebase architecture and common patterns.
124
-
In case your fix changes more than 75 lines in src/ try a different fix.
131
+
The regression test MUST fail without the fix. Run it before implementing the fix to confirm it reproduces the bug.
125
132
126
-
## Step 3: Verify the fix
133
+
## Step 3: Fix the reported bug
127
134
128
-
1. Run the regression test to confirm it passes now
129
-
2. Run the full test suite: make tests
130
-
3. Run PHPStan self-analysis: make phpstan
131
-
4. Fix any failures that come up
132
-
5. Verify all src/ changes made are really required to make the tests pass. Revert/Remove changes which are unrelated.
133
-
6. Run make cs-fix to fix any coding standard violations
134
-
7. Run make name-collision and fix violations - add different tests in unique namespaces. If the function and class declarations are exactly the same, you can reuse them across files instead of duplicating them.
135
+
Implement the fix in \`src/\` following the guidance in CLAUDE.md:
136
+
- When you need to query a property of a type, prefer \`$type->isX()\` / \`isSuperTypeOf()\` over \`instanceof\`.
137
+
- If the check is needed in more than one place, add a method to the \`Type\` interface instead of scattering \`instanceof\` checks — every type implementation (including \`UnionType\`, \`IntersectionType\`, accessory types) needs to handle the query correctly.
138
+
- Respect the \`@api\` backward-compatibility promise — do not break public method signatures on \`@api\`-tagged classes/interfaces.
139
+
- Keep comments to a minimum: only add a comment when the *why* is non-obvious.
135
140
136
-
Do not create a branch, push, or create a PR - this will be handled automatically.
141
+
Run the regression test from Step 2 to confirm it now passes. Then continue to Step 4 — **do not stop here.**
137
142
138
-
## Step 4: Write a summary
143
+
## Step 4: Hunt for analogous bugs and expand the fix
139
144
140
-
After completing the fix, write three files:
145
+
This is the step that distinguishes a good fix from a great one. The bug the user reported is usually one visible instance of a wider pattern. Your job is to find the other instances, prove they are broken with failing tests, and fix them with the same approach.
146
+
147
+
**How to think about it:** identify the *conceptual axis* along which PHP (or PHPStan's type system) offers parallel constructs. If your fix touches one construct on that axis, the other constructs on the same axis are prime suspects. Write a failing test for each suspect you can think of. For every suspect that actually fails, apply the same fix in the analogous location in the codebase and verify the test passes.
148
+
149
+
**Parallel-construct axes to consider** (use this as a thinking prompt, not an exhaustive list):
150
+
151
+
- **Class members**: properties ↔ methods ↔ class constants ↔ enum cases. A bug about readonly **properties** may also affect method visibility checks, class constant visibility, or enum case access. A bug about property hooks may have a twin in promoted constructor properties, asymmetric visibility, or \`#[\\Override]\`. Static vs instance members are another axis on the same group.
152
+
- **Callables**: functions ↔ methods ↔ static methods ↔ closures ↔ arrow functions ↔ first-class callable syntax (\`foo(...)\`) ↔ \`__invoke\`. A bug in \`FunctionCallParametersCheck\` very often has a twin in the method-call or static-method-call equivalents.
153
+
- **Type-narrowing functions**: \`is_int\` / \`is_string\` / \`is_float\` / \`is_bool\` / \`is_array\` / \`is_object\` / \`is_callable\` / \`is_iterable\` / \`is_numeric\` / \`is_countable\` — these live in parallel in \`TypeSpecifier\` and \`FunctionTypeSpecifyingExtension\`s. A fix to one \`is_*\` narrowing almost always needs a mirror fix in the others.
- **Accessory string types**: \`AccessoryNonEmptyStringType\`, \`AccessoryNonFalsyStringType\`, \`AccessoryLiteralStringType\`, \`AccessoryLowercaseStringType\`, \`AccessoryUppercaseStringType\`, \`AccessoryNumericStringType\`. A bug in one (e.g. string operations not preserving non-empty-ness) is almost certainly also a bug in the others (not preserving lowercase-ness, numeric-ness, etc.).
157
+
- **Accessory array types**: \`NonEmptyArrayType\`, \`AccessoryArrayListType\`, \`HasOffsetType\`, \`HasOffsetValueType\`, \`OversizedArrayType\`. Same logic — if an array operation loses non-empty-ness, does it also lose list-ness?
158
+
- **Constant types**: \`ConstantStringType\`, \`ConstantIntegerType\`, \`ConstantFloatType\`, \`ConstantBooleanType\`, \`ConstantArrayType\`. A constant-folding bug in one scalar flavor usually exists in the others.
- **TypeSpecifier contexts**: truthy ↔ falsey ↔ true ↔ false ↔ null. A narrowing bug in the truthy branch frequently has a mirror in the falsey branch.
164
+
- **PHPDoc tags**: \`@param\` ↔ \`@return\` ↔ \`@var\` ↔ \`@property\` ↔ \`@phpstan-assert\` ↔ \`@throws\`. A PHPDoc parser or resolution bug in one tag often affects the others.
165
+
- **Rules directories**: \`src/Rules/Classes/\` ↔ \`Methods/\` ↔ \`Properties/\` ↔ \`Functions/\` ↔ \`Variables/\`. Rules in these folders often come in families — an undefined-member check or a visibility check usually has siblings across categories.
1. Before implementing the fix in Step 3, already jot down (in your TodoWrite list) which axis this bug sits on and which other constructs you should probe afterward.
171
+
2. After the Step 3 fix is green, for each suspect on the axis, write a small failing test (same format as your Step 2 regression test). Run it.
172
+
- If it passes already, great — the analogous case is not broken, move on. Discard the extra test (do not keep tests that exist only to prove non-bugs) unless it covers a plausible future regression of the same code path.
173
+
- If it fails, apply the analogous fix in the analogous location (e.g. the sibling accessory type, the sibling \`is_*\` specifier, the sibling rule) and confirm the test now passes. Keep the test.
174
+
3. When adding a new method to the \`Type\` interface as part of your fix, this is an especially strong hint to audit every implementation of the interface — \`UnionType\`, \`IntersectionType\`, and accessory types all need correct implementations, not just the one that prompted the bug.
175
+
4. Do not force-invent bugs. If after honest probing the analogous constructs really are fine, document that briefly in your commit message so a reviewer can see you considered them. But err on the side of probing more, not less — the user strongly prefers fixes that sweep an entire family of bugs at once over fixes that leave siblings broken.
176
+
177
+
There is no upper bound on diff size for this step. A 500-line diff that correctly fixes eight parallel cases is much better than a 20-line diff that fixes one and leaves seven sibling bugs for later.
178
+
179
+
## Step 5: Verify everything
180
+
181
+
Run these in order and iterate until everything is green. Do NOT give up on the first failure — diagnose and fix each problem.
182
+
183
+
1. Run all the regression tests you added (the original one plus any analogous-case tests from Step 4) and confirm they all pass.
184
+
2. Run the full test suite: \`make tests\`
185
+
3. Run PHPStan self-analysis: \`make phpstan\`
186
+
4. Fix any failures. If a pre-existing test now fails, figure out whether:
187
+
(a) it was asserting the buggy behavior and should be updated to match the corrected behavior, or
188
+
(b) your fix over-reached and broke something unrelated — in which case narrow the fix.
189
+
Prefer (a) when the old assertion is clearly wrong under the new, correct behavior.
190
+
5. Run \`make cs-fix\` to fix coding-standard violations.
191
+
6. Run \`make name-collision\` and fix violations — put new tests in unique namespaces. If a function/class declaration is exactly the same as one already used, reuse it rather than duplicating.
192
+
7. Re-run \`make tests\` one final time after cs-fix / name-collision fixes to make sure nothing regressed.
193
+
194
+
## Step 6: Write a summary
195
+
196
+
After completing the fix, write three files. These files are critical — they are used for the PR title, commit message, and PR description, and the workflow will fail to produce a good PR if they are missing or low-quality.
141
197
142
198
1. /tmp/pr-title.txt - A single-line PR title that describes **what the change does**, not what it fixes. Write it as a precise, technical description of the actual code change you made.
143
199
@@ -161,7 +217,7 @@ jobs:
161
217
- "Fix bug with generics" (vague, not technical)
162
218
- "Two unbounded generics in conditional return are assumed to be always the same" (describes symptom, not fix)
163
219
164
-
2. /tmp/commit-message.txt - A concise commit message. The first line MUST be identical to the contents of /tmp/pr-title.txt. Then a blank line, then a few bullet points describing the key changes (what code was changed and why, in technical terms).
220
+
2. /tmp/commit-message.txt - A concise commit message. The first line MUST be identical to the contents of /tmp/pr-title.txt. Then a blank line, then a few bullet points describing the key changes (what code was changed and why, in technical terms). If you fixed analogous cases in Step 4, list each one briefly.
165
221
166
222
3. /tmp/pr-description.md - A pull request description in this format:
167
223
## Summary
@@ -170,16 +226,17 @@ jobs:
170
226
## Changes
171
227
- Bullet points of specific code changes made
172
228
- Reference file paths where changes were made
229
+
- List every analogous case you also fixed (or explicitly note the ones you probed and found to be already correct)
173
230
174
231
## Root cause
175
-
Explain why the bug happened and how the fix addresses it.
232
+
Explain why the bug happened and how the fix addresses it. If the root cause is a pattern (e.g. "accessory types don't preserve their refinement through string concatenation"), name the pattern and list every location that was affected.
176
233
177
234
## Test
178
-
Describe the regression test that was added.
235
+
Describe the regression test for the reported bug AND any additional tests you added for analogous cases in Step 4.
179
236
180
237
Fixes phpstan/phpstan#${{ inputs.issue-number }}
181
238
182
-
These files are critical - they will be used for the PR title, commit message, and PR description. The PR title and commit subject line must describe **what the change does**, not what it fixes.
239
+
Remember: the PR title and commit subject line must describe **what the change does**, not what it fixes.
0 commit comments