Problem/Motivation

In #2941148: Fix Drupal.Commenting.FunctionComment.MissingReturnType return types were fixed. But this change NULL returns to return; whereas the return in the docblock is NULL. Perhaps we should change those to be explicit NULL instead of current implied NULL.

Steps to reproduce

Proposed resolution

Remaining tasks

  1. Decide if we want to change this
  2. ...

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3312049

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

bbrala created an issue. See original summary.

Sweetchuck made their first commit to this issue’s fork.

sweetchuck’s picture

Decide if we want to change this

If we ever want to use proper typehints for function/method parameters and return value, and declare(strict_types = 1);, then the answer is yes.

<?php

/**
 * @param bool $string
 *
 * @return string|null
 */
function my_nullable_string_phpdoc(bool $string) {
  if ($string) {
    return 'okay';
  }
}

function my_nullable_string_typehint_fail(bool $string): ?string {
  if ($string) {
    return 'okay';
  }
}

function my_nullable_string_typehint_ok(bool $string): ?string {
  if ($string) {
    return 'okay';
  }

  return NULL;
}

my_nullable_string_phpdoc(TRUE);
my_nullable_string_phpdoc(FALSE);

my_nullable_string_typehint_fail(TRUE);
my_nullable_string_typehint_fail(FALSE);

my_nullable_string_typehint_ok(TRUE);
my_nullable_string_typehint_ok(FALSE);
longwave’s picture

sweetchuck’s picture

declare(strict_types=1); doesn't affect this behaviour.

sweetchuck’s picture

PhpStorm reported 428 occurrences of this kind of error (not explicitly defined return value).
In the commit 2022-09-27 57b569d I fixed only very few of them.

Several of them would be straightforward as well, like the diff between the
my_nullable_string_typehint_fail
and
my_nullable_string_typehint_ok
see comment #4


But there are quite a few tricky ones.

\Drupal\Core\Template\AttributeValueBase::render returns string only in a certain condition, otherwise returns null – not explicitly, with typehint it would throw a "Fatal error: Uncaught TypeError" –, but according to the PHPDoc it always returns a string.
There are several function/method like this.
For example: \hook_help(). Even the example code is "wrong", so all the implementations.
No to mention that \action_help() returns a \Drupal\Core\StringTranslation\TranslatableMarkup instance.

One more example: \Drupal\views\Plugin\views\field\EntityField::getFieldStorageDefinition


\Drupal\locale\PluralFormula::loadFormulae
PHPDoc @return array, but the actual return value is void.
This one also detected by PHPCS Drupal.Commenting.FunctionComment.InvalidNoReturn.

borisson_’s picture

I disagree that we need to change the returns to satisfy the type here, I think it's perfectly valid to have return; but the typehint in that case should be @return string|void, so it's a string or nothing, null is not the same as void/nothing.

sweetchuck’s picture

@borisson_ There is no such a thing in PHP: string|void

https://wiki.php.net/rfc/union_types_v2#supported_types

The void type can never be part of a union. As such, types like T|void are illegal in all positions, including return types.

The void type indicates that the function has no return value, and enforces that argument-less return; is used to return from the function. It is fundamentally incompatible with non-void return types.

What is likely intended instead is ?T, which allows returning either T or null.

bbrala’s picture

I think #9 is the best argument that we need to do this anyways. Although it will be a hard to task to get right everywhere. We probably need to split this task up into manageable steps.

In my humble opinion I don't think there are resons not to do this, at least in the methods that seem like they are simple to fix.

I do think there is merit to have some more eyes on this though, in a way we would be changing contracts. But you could argue we are just being more explicit on contracts.

bbrala’s picture

One thing to note.

 ((function(): void {})()) === NULL

So it shouldn't break anything that checks return values.

By dwi on slack.

kingdutch’s picture

I shared this in Slack but never got around to posting to this thread. With regards to whether we should or should not enforce a final return NULL;, I believe we should.

I think we should just follow the wider PHP community and be explicit in our final NULL returns. PHPStan already warns about this exact scenario.

https://phpstan.org/r/848a5b50-4525-415e-a7aa-cadfea465095

Even if I follow the initial hint of "remove NULL because it never returns NULL" then I get "Function test() should return string but return statement is missing." which also indicates that I forgot to think about something.

https://phpstan.org/r/9a0087a9-05ae-47d9-bc25-da93dc1f7cae

Adding a return NULL at the end indicates that I've thought about my return types and the code is complete. If this should never return NULL then apparently $b not being always TRUE is an error and I should end with throwing a helpful exception instead.

sweetchuck’s picture

@Kingdutch : Thanks for the comment. :-)

I started to work on this, but I have concerns:

  1. The diff will be quiet large, and reviewer/maintainer will ask for to split into smaller issues.
  2. There are lot of controversial code and PhpDoc.

For example:

  1. PhpDoc \Drupal\Component\Plugin\Mapper\MapperInterface::getInstance
  2. Implementation \Drupal\Core\Archiver\ArchiverManager::getInstance

By adding return NULL; won't change that how the code behaves, and it is still won't match to the PhpDoc.
Is it part of the scope to change @return object|false to @return object|null?

I think yes, It would be good to clarify in advance.

sweetchuck’s picture

Other question is how to solve the simple ones.

Original:

  public function adminSummary() {
    if (!empty($this->options['exposed'])) {
      return $this->t('exposed');
    }
  }

Style 1

  public function adminSummary() {
    if (!empty($this->options['exposed'])) {
      return $this->t('exposed');
    }

    return NULL;
  }

Style 2

  public function adminSummary() {
    return !empty($this->options['exposed']) ?
      $this->t('exposed')
      : NULL;
  }

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

sweetchuck’s picture

Version: 11.x-dev » 10.1.x-dev
Status: Active » Needs review
smustgrave’s picture

Version: 10.1.x-dev » 11.x-dev
Status: Needs review » Needs work

Can the MR be updated for 11.x please.

11.x is the current development branch, so code has to be committed there first to be backported.

sweetchuck’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

So searching for " but return statement is missing" there still appear to be 89 instances in phpstan-baseline. Were those left out specifically?

sweetchuck’s picture

Give them a closer look.

For example this one: \Drupal\Core\Database\Query\Delete::execute

sweetchuck’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

Think it needs to be documented why we are skipping these 89 instances

Some functions are

save(),
getFormId(),
validateForm(),
etc

If the inherited docs say it returns a value and we are leaving in the phpstan file think it needs to be stated why.

quietone’s picture

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

dcam’s picture