2626 * visitor closes the gap so PHPStan can use the existing core annotations
2727 * without each `global` statement needing its own redundant `@var`.
2828 *
29+ * `@global` tags in a docblock attached directly to a `global` statement are
30+ * honored as well, and take precedence over the enclosing function's tags.
31+ * This makes `@global` sufficient for file-scope `global` statements (e.g. in
32+ * templates included into another scope), which have no enclosing function
33+ * docblock at all.
34+ *
2935 * Functions that do not document a global, or that import a global the
3036 * function docblock does not list, are left untouched and continue to
3137 * resolve as `mixed` — preserving PHPStan's safety guarantees.
@@ -73,11 +79,25 @@ public function enterNode( Node $node ): ?Node {
7379 return null ;
7480 }
7581
76- if ( ! ( $ node instanceof Node \Stmt \Global_ ) || $ this -> stack === array () ) {
82+ if ( ! ( $ node instanceof Node \Stmt \Global_ ) ) {
7783 return null ;
7884 }
7985
80- $ map = $ this ->stack [ count ( $ this ->stack ) - 1 ];
86+ $ map = $ this ->stack !== array () ? $ this ->stack [ count ( $ this ->stack ) - 1 ] : array ();
87+
88+ $ existing = $ node ->getDocComment ();
89+ $ existing_text = $ existing !== null ? $ existing ->getText () : '' ;
90+
91+ /*
92+ * Also honor `@global` tags on the docblock attached directly to the
93+ * `global` statement itself. This covers file-scope statements (which
94+ * have no enclosing function docblock) and takes precedence over the
95+ * enclosing function's tags for the same variable.
96+ */
97+ if ( $ existing_text !== '' ) {
98+ $ map = array_merge ( $ map , $ this ->parse_global_tags ( $ existing_text ) );
99+ }
100+
81101 if ( $ map === array () ) {
82102 return null ;
83103 }
@@ -87,9 +107,7 @@ public function enterNode( Node $node ): ?Node {
87107 * statement so we can leave them alone but still inject `@var` lines for
88108 * the remaining variables in a multi-variable `global $a, $b;` statement.
89109 */
90- $ existing = $ node ->getDocComment ();
91- $ existing_text = $ existing !== null ? $ existing ->getText () : '' ;
92- $ already_typed = array ();
110+ $ already_typed = array ();
93111 if ( $ existing_text !== '' && preg_match_all ( '/@(?:phpstan-)?var\s+[^\n]*?\$(\w+)/ ' , $ existing_text , $ existing_matches ) > 0 ) {
94112 $ already_typed = array_flip ( $ existing_matches [1 ] );
95113 }
0 commit comments