Skip to content

[dx] Reorder rule guards to bail cheaply before expensive analysis#8103

Merged
TomasVotruba merged 1 commit into
mainfrom
early-bail-guard-reorders
Jun 29, 2026
Merged

[dx] Reorder rule guards to bail cheaply before expensive analysis#8103
TomasVotruba merged 1 commit into
mainfrom
early-bail-guard-reorders

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

What

Across 16 rules, an expensive analysis call (getType/getNativeType, isObjectType, createFromNodeOrEmpty docblock parsing, or a subtree traversal) ran before a cheap, independent guard that could already return early. This reorders each so the cheap guard runs first — the expensive call is skipped whenever the cheap guard bails.

All reorders are behavior-preserving: in each case the cheap guard does not depend on the expensive result, so moving it earlier changes only when we short-circuit, not whether.

Rules touched

Type resolution moved after a cheap name/instanceof/arg guard

  • Php74/ExportToReflectionFunctionRectorisName('export') + isFirstClassCallable() before getType($node->class)
  • Php82/FilesystemIteratorSkipDotsRectorisset($args[1]) before isObjectType
  • Php84/AddEscapeArgumentRector — method-name in_array before isObjectType
  • Php85/OrdSingleByteRector — single-char String_/Int_ literal checks before getNativeType
  • Php85/RemoveFinfoBufferContextArgRectorisName('buffer') short-circuits before isObjectType in the ||
  • CodingStyle/ConsistentImplodeRectorcount($args) !== 2 before getType (also removes a latent undefined-index on malformed 0-arg calls)
  • CodeQuality/LogicalToBooleanRectorgetNativeType only computed inside the instanceof Assign branch that uses it
  • CodeQuality/InlineIfToExplicitIfRectordefined() FuncCall guard before getType
  • CodeQuality/NumberCompareToMaxFuncCallRectorareIntegersCompared (2× getType) gated behind the structural pattern match, so non-comparison BinaryOps bail free

Docblock parsing moved after a cheap guard

  • Php80/ClassPropertyAssignToConstructorPromotionRector — class-reflection guard before createFromNodeOrEmpty
  • TypeDeclarationDocblocks/AddReturnDocblockFromMethodCallDocblockRector
  • TypeDeclarationDocblocks/AddReturnDocblockForCommonObjectDenominatorRector
  • TypeDeclarationDocblocks/AddReturnDocblockForArrayDimAssignedObjectRector — native returnType array check before parsing the docblock
  • TypeDeclarationDocblocks/AddReturnDocblockForJsonArrayRector — same, plus removes a duplicate createFromNodeOrEmpty call (parsed twice)
  • TypeDeclarationDocblocks/AddParamArrayDocblockFromDataProviderRector — parse docblock only after a data provider is found

Expensive traversal moved after cheap structural match

  • DeadCode/RemoveNextSameValueConditionRector — next-statement existence/equality checks before the side-effect + cond-variable-usage traversals of the current if

Tests

No behavior change, so existing fixtures are the proof. Full suites for every touched category pass:
rules-tests/{Php74,Php80,Php82,Php84,Php85,CodingStyle,CodeQuality,DeadCode,TypeDeclarationDocblocks} → 2668 tests green. ECS + PHPStan level 8 clean.

Note

A 17th candidate (DeadCode/RemoveUselessTernaryRector) was rejected during review: its getNativeType result is needed by an earlier check that legitimately runs before the structural guard, so reordering there would change behavior.

@TomasVotruba TomasVotruba merged commit 84a2cd8 into main Jun 29, 2026
65 checks passed
@TomasVotruba TomasVotruba deleted the early-bail-guard-reorders branch June 29, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant