@@ -385,25 +385,70 @@ public function getAllFuncInfos(): iterable {
385385 }
386386}
387387
388+ class DocCommentTag {
389+ /** @var string */
390+ public $ name ;
391+ /** @var ?string */
392+ public $ value ;
393+
394+ public function __construct (string $ name , ?string $ value ) {
395+ $ this ->name = $ name ;
396+ $ this ->value = $ value ;
397+ }
398+
399+ public function getValue (): string {
400+ if ($ this ->value === null ) {
401+ throw new Exception ("@ $ this ->name does not have a value " );
402+ }
403+
404+ return $ this ->value ;
405+ }
406+
407+ public function getVariableName (): string {
408+ $ value = $ this ->getValue ();
409+ if ($ value === null || strlen ($ value ) === 0 || $ value [0 ] !== '$ ' ) {
410+ throw new Exception ("@ $ this ->name not followed by variable name " );
411+ }
412+
413+ return substr ($ value , 1 );
414+ }
415+ }
416+
417+ /** @return DocCommentTag[] */
418+ function parseDocComment (DocComment $ comment ): array {
419+ $ commentText = substr ($ comment ->getText (), 2 , -2 );
420+ $ tags = [];
421+ foreach (explode ("\n" , $ commentText ) as $ commentLine ) {
422+ $ regex = '/^\*\s*@([a-z-]+)(?:\s+(.+))$/ ' ;
423+ if (preg_match ($ regex , trim ($ commentLine ), $ matches , PREG_UNMATCHED_AS_NULL )) {
424+ $ tags [] = new DocCommentTag ($ matches [1 ], $ matches [2 ]);
425+ }
426+ }
427+
428+ return $ tags ;
429+ }
430+
388431function parseFunctionLike (
389432 string $ name , ?string $ className , Node \FunctionLike $ func , ?string $ cond
390433): FuncInfo {
391434 $ comment = $ func ->getDocComment ();
392435 $ paramMeta = [];
393436 $ alias = null ;
437+ $ haveDocReturnType = false ;
394438
395439 if ($ comment ) {
396- $ commentText = substr ($ comment ->getText (), 2 , -2 );
397-
398- foreach (explode ("\n" , $ commentText ) as $ commentLine ) {
399- if (preg_match ('/^\*\s*@prefer-ref\s+\$(.+)$/ ' , trim ($ commentLine ), $ matches )) {
400- $ varName = $ matches [1 ];
440+ $ tags = parseDocComment ($ comment );
441+ foreach ($ tags as $ tag ) {
442+ if ($ tag ->name === 'prefer-ref ' ) {
443+ $ varName = $ tag ->getVariableName ();
401444 if (!isset ($ paramMeta [$ varName ])) {
402445 $ paramMeta [$ varName ] = [];
403446 }
404447 $ paramMeta [$ varName ]['preferRef ' ] = true ;
405- } else if (preg_match ('/^\*\s*@alias\s+(.+)$/ ' , trim ($ commentLine ), $ matches )) {
406- $ alias = $ matches [1 ];
448+ } else if ($ tag ->name === 'alias ' ) {
449+ $ alias = $ tag ->getValue ();
450+ } else if ($ tag ->name === 'return ' ) {
451+ $ haveDocReturnType = true ;
407452 }
408453 }
409454 }
@@ -455,6 +500,10 @@ function parseFunctionLike(
455500 }
456501
457502 $ returnType = $ func ->getReturnType ();
503+ if ($ returnType === null && !$ haveDocReturnType && substr ($ name , 0 , 2 ) !== '__ ' ) {
504+ throw new Exception ("Missing return type for function $ name() " );
505+ }
506+
458507 $ return = new ReturnInfo (
459508 $ func ->returnsByRef (),
460509 $ returnType ? Type::fromNode ($ returnType ) : null );
0 commit comments