@@ -362,22 +362,34 @@ protected function areFollowingArgumentsUsed($varInfo, $scopeInfo) {
362
362
* @return void
363
363
*/
364
364
protected function markVariableAssignment ($ varName , $ stackPtr , $ currScope ) {
365
+ $ this ->markVariableAssignmentWithoutInitialization ($ varName , $ stackPtr , $ currScope );
366
+ $ varInfo = $ this ->getOrCreateVariableInfo ($ varName , $ currScope );
367
+ if (isset ($ varInfo ->firstInitialized ) && ($ varInfo ->firstInitialized <= $ stackPtr )) {
368
+ Helpers::debug ('markVariableAssignment variable is already initialized ' , $ varName );
369
+ return ;
370
+ }
371
+ $ varInfo ->firstInitialized = $ stackPtr ;
372
+ }
373
+
374
+ /**
375
+ * @param string $varName
376
+ * @param int $stackPtr
377
+ * @param int $currScope
378
+ *
379
+ * @return void
380
+ */
381
+ protected function markVariableAssignmentWithoutInitialization ($ varName , $ stackPtr , $ currScope ) {
365
382
$ varInfo = $ this ->getOrCreateVariableInfo ($ varName , $ currScope );
366
383
367
384
// Is the variable referencing another variable? If so, mark that variable used also.
368
- if ($ varInfo ->referencedVariable && $ varInfo ->referencedVariableScope ) {
385
+ if ($ varInfo ->referencedVariableScope !== null && $ varInfo ->referencedVariableScope !== $ currScope ) {
369
386
$ this ->markVariableAssignment ($ varInfo ->name , $ stackPtr , $ varInfo ->referencedVariableScope );
370
387
}
371
388
372
389
if (!isset ($ varInfo ->scopeType )) {
373
390
$ varInfo ->scopeType = ScopeType::LOCAL ;
374
391
}
375
- if (isset ($ varInfo ->firstInitialized ) && ($ varInfo ->firstInitialized <= $ stackPtr )) {
376
- Helpers::debug ('markVariableAssignment failed; already initialized ' , $ varName );
377
- return ;
378
- }
379
- Helpers::debug ('markVariableAssignment ' , $ varName );
380
- $ varInfo ->firstInitialized = $ stackPtr ;
392
+ $ varInfo ->allAssignments [] = $ stackPtr ;
381
393
}
382
394
383
395
/**
@@ -400,6 +412,7 @@ protected function markVariableDeclaration(
400
412
) {
401
413
Helpers::debug ("marking variable ' {$ varName }' declared in scope starting at token " , $ currScope );
402
414
$ varInfo = $ this ->getOrCreateVariableInfo ($ varName , $ currScope );
415
+
403
416
if (isset ($ varInfo ->scopeType )) {
404
417
if (($ permitMatchingRedeclaration === false ) || ($ varInfo ->scopeType !== $ scopeType )) {
405
418
// Issue redeclaration/reuse warning
@@ -418,6 +431,7 @@ protected function markVariableDeclaration(
418
431
);
419
432
}
420
433
}
434
+
421
435
$ varInfo ->scopeType = $ scopeType ;
422
436
if (isset ($ typeHint )) {
423
437
$ varInfo ->typeHint = $ typeHint ;
@@ -427,6 +441,7 @@ protected function markVariableDeclaration(
427
441
return ;
428
442
}
429
443
$ varInfo ->firstDeclared = $ stackPtr ;
444
+ $ varInfo ->allAssignments [] = $ stackPtr ;
430
445
Helpers::debug ("variable ' {$ varName }' marked declared " , $ varInfo );
431
446
}
432
447
@@ -538,7 +553,7 @@ protected function markAllVariablesRead(File $phpcsFile, $stackPtr) {
538
553
*
539
554
* @return void
540
555
*/
541
- protected function processVariableAsFunctionDefinitionArgument (File $ phpcsFile , $ stackPtr , $ varName ) {
556
+ protected function processVariableAsFunctionDefinitionArgument (File $ phpcsFile , $ stackPtr , $ varName, $ outerScope ) {
542
557
Helpers::debug ("processVariableAsFunctionDefinitionArgument " , $ stackPtr , $ varName );
543
558
$ tokens = $ phpcsFile ->getTokens ();
544
559
@@ -554,7 +569,7 @@ protected function processVariableAsFunctionDefinitionArgument(File $phpcsFile,
554
569
$ referencePtr = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , $ stackPtr - 1 , null , true , null , true );
555
570
if (($ referencePtr !== false ) && ($ tokens [$ referencePtr ]['code ' ] === T_BITWISE_AND )) {
556
571
$ varInfo = $ this ->getOrCreateVariableInfo ($ varName , $ functionPtr );
557
- $ varInfo ->passByReference = true ;
572
+ $ varInfo ->referencedVariableScope = $ outerScope ;
558
573
}
559
574
560
575
// Are we optional with a default?
@@ -576,7 +591,7 @@ protected function processVariableAsFunctionDefinitionArgument(File $phpcsFile,
576
591
protected function processVariableAsUseImportDefinition (File $ phpcsFile , $ stackPtr , $ varName , $ outerScope ) {
577
592
$ tokens = $ phpcsFile ->getTokens ();
578
593
579
- Helpers::debug ("processVariableAsUseImportDefinition " , $ stackPtr , $ varName );
594
+ Helpers::debug ("processVariableAsUseImportDefinition " , $ stackPtr , $ varName, $ outerScope );
580
595
581
596
$ endOfArgsPtr = $ phpcsFile ->findPrevious ([T_CLOSE_PARENTHESIS ], $ stackPtr - 1 , null );
582
597
if (! is_int ($ endOfArgsPtr )) {
@@ -606,9 +621,6 @@ protected function processVariableAsUseImportDefinition(File $phpcsFile, $stackP
606
621
if (is_int ($ referencePtr ) && $ tokens [$ referencePtr ]['code ' ] === T_BITWISE_AND ) {
607
622
Helpers::debug ("variable ' {$ varName }' in function definition looks passed by reference " );
608
623
$ varInfo = $ this ->getOrCreateVariableInfo ($ varName , $ functionPtr );
609
- $ varInfo ->passByReference = true ;
610
- $ referencedVariable = $ this ->getVariableInfo ($ varName , $ outerScope );
611
- $ varInfo ->referencedVariable = $ referencedVariable ;
612
624
$ varInfo ->referencedVariableScope = $ outerScope ;
613
625
}
614
626
}
@@ -844,22 +856,36 @@ protected function processVariableAsAssignment(File $phpcsFile, $stackPtr, $varN
844
856
return false ;
845
857
}
846
858
847
- // Plain ol' assignment. Simpl(ish).
848
859
$ writtenPtr = Helpers::findWhereAssignExecuted ($ phpcsFile , $ assignPtr );
849
- $ this ->markVariableAssignment ($ varName , $ writtenPtr , $ currScope );
850
860
851
- // Are we are reference variable?
852
- $ tokens = $ tokens = $ phpcsFile ->getTokens ();
861
+ // If the right-hand-side of the assignment to this variable is a reference
862
+ // variable, then this variable is a reference to that one, and as such any
863
+ // assignment to this variable (except another assignment by reference,
864
+ // which would change the binding) has a side effect of changing the
865
+ // referenced variable and therefore should count as both an assignment and
866
+ // a read.
867
+ $ tokens = $ phpcsFile ->getTokens ();
853
868
$ referencePtr = $ phpcsFile ->findNext (Tokens::$ emptyTokens , $ assignPtr + 1 , null , true , null , true );
854
- $ varInfo = $ this ->getOrCreateVariableInfo ($ varName , $ currScope );
855
- if ($ referencePtr !== false && $ tokens [$ referencePtr ]['code ' ] === T_BITWISE_AND ) {
856
- $ varInfo ->isReference = true ;
857
- } elseif ($ varInfo ->isReference ) {
858
- // If this is an assigment to a reference variable then that variable is
859
- // used.
860
- $ this ->markVariableRead ($ varName , $ stackPtr , $ currScope );
869
+ if (is_int ($ referencePtr ) && $ tokens [$ referencePtr ]['code ' ] === T_BITWISE_AND ) {
870
+ $ varInfo = $ this ->getOrCreateVariableInfo ($ varName , $ currScope );
871
+ // If the variable was already declared, but was not yet read, it is
872
+ // unused because we're about to change the binding.
873
+ $ scopeInfo = $ this ->getOrCreateScopeInfo ($ currScope );
874
+ $ this ->processScopeCloseForVariable ($ phpcsFile , $ varInfo , $ scopeInfo );
875
+ Helpers::debug ('found reference variable ' );
876
+ // The referenced variable may have a different name, but we don't
877
+ // actually need to mark it as used in this case because the act of this
878
+ // assignment will mark it used on the next token.
879
+ $ varInfo ->referencedVariableScope = $ currScope ;
880
+ $ this ->markVariableDeclaration ($ varName , ScopeType::LOCAL , null , $ writtenPtr , $ currScope , true );
881
+ // An assignment to a reference is a binding and should not count as
882
+ // initialization since it doesn't change any values.
883
+ $ this ->markVariableAssignmentWithoutInitialization ($ varName , $ writtenPtr , $ currScope );
884
+ return true ;
861
885
}
862
886
887
+ $ this ->markVariableAssignment ($ varName , $ writtenPtr , $ currScope );
888
+
863
889
return true ;
864
890
}
865
891
@@ -1233,7 +1259,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) {
1233
1259
$ token = $ tokens [$ stackPtr ];
1234
1260
1235
1261
$ varName = Helpers::normalizeVarName ($ token ['content ' ]);
1236
- Helpers::debug ("examining token for variable ' {$ varName }' " , $ token );
1262
+ Helpers::debug ("examining token for variable ' {$ varName }' on line { $ token [ ' line ' ]} " , $ token );
1237
1263
$ currScope = Helpers::findVariableScope ($ phpcsFile , $ stackPtr );
1238
1264
if ($ currScope === null ) {
1239
1265
Helpers::debug ('no scope found ' );
@@ -1270,7 +1296,7 @@ protected function processVariable(File $phpcsFile, $stackPtr) {
1270
1296
// Are we a function or closure parameter?
1271
1297
if (Helpers::isTokenInsideFunctionDefinitionArgumentList ($ phpcsFile , $ stackPtr )) {
1272
1298
Helpers::debug ('found function definition argument ' );
1273
- $ this ->processVariableAsFunctionDefinitionArgument ($ phpcsFile , $ stackPtr , $ varName );
1299
+ $ this ->processVariableAsFunctionDefinitionArgument ($ phpcsFile , $ stackPtr , $ varName, $ currScope );
1274
1300
return ;
1275
1301
}
1276
1302
@@ -1527,7 +1553,7 @@ protected function processScopeCloseForVariable(File $phpcsFile, VariableInfo $v
1527
1553
if ($ this ->allowUnusedForeachVariables && $ varInfo ->isForeachLoopAssociativeValue ) {
1528
1554
return ;
1529
1555
}
1530
- if ($ varInfo ->passByReference && isset ($ varInfo ->firstInitialized )) {
1556
+ if ($ varInfo ->referencedVariableScope !== null && isset ($ varInfo ->firstInitialized )) {
1531
1557
// If we're pass-by-reference then it's a common pattern to
1532
1558
// use the variable to return data to the caller, so any
1533
1559
// assignment also counts as "variable use" for the purposes
@@ -1540,19 +1566,24 @@ protected function processScopeCloseForVariable(File $phpcsFile, VariableInfo $v
1540
1566
// the purposes of "unused variable" warnings.
1541
1567
return ;
1542
1568
}
1543
-
1544
- $ stackPtr = null ;
1545
- if (! empty ($ varInfo ->firstDeclared )) {
1546
- $ stackPtr = $ varInfo ->firstDeclared ;
1547
- } elseif (! empty ($ varInfo ->firstInitialized )) {
1548
- $ stackPtr = $ varInfo ->firstInitialized ;
1569
+ if (empty ($ varInfo ->firstDeclared ) && empty ($ varInfo ->firstInitialized )) {
1570
+ return ;
1549
1571
}
1572
+ $ this ->warnAboutUnusedVariable ($ phpcsFile , $ varInfo );
1573
+ }
1550
1574
1551
- if ($ stackPtr ) {
1575
+ /**
1576
+ * @param File $phpcsFile
1577
+ * @param VariableInfo $varInfo
1578
+ *
1579
+ * @return void
1580
+ */
1581
+ protected function warnAboutUnusedVariable (File $ phpcsFile , VariableInfo $ varInfo ) {
1582
+ foreach (array_unique ($ varInfo ->allAssignments ) as $ indexForWarning ) {
1552
1583
Helpers::debug ("variable {$ varInfo ->name } at end of scope looks unused " );
1553
1584
$ phpcsFile ->addWarning (
1554
1585
"Unused %s %s. " ,
1555
- $ stackPtr ,
1586
+ $ indexForWarning ,
1556
1587
'UnusedVariable ' ,
1557
1588
[
1558
1589
VariableInfo::$ scopeTypeDescriptions [$ varInfo ->scopeType ],
0 commit comments