Skip to content

Add #[Deprecated] attribute based on ZEND_ACC_DEPRECATED #6521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 0 commits into from

Conversation

beberlei
Copy link
Contributor

@beberlei beberlei commented Dec 18, 2020

https://wiki.php.net/rfc/deprecated_attribute

Todos:

  • Does not show the deprecation when opcache or jit is enabled (haven't figured it out in detail)
  • Memory leak for string arugments of internal attributes.
  • Convert deprecation to exception is not tested yet, and code is not adopted from internal deprecations.
  • Tests for methods, and failure scenarios of attribute usage

@beberlei beberlei marked this pull request as draft December 18, 2020 20:05
@cmb69 cmb69 added the RFC label Dec 19, 2020
Copy link
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for methods seem to be missing 🙂

Copy link
Contributor

@TysonAndre TysonAndre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like useful functionality overall, some comments that are easy to address

@@ -3945,6 +3945,10 @@ ZEND_VM_HOT_HANDLER(130, ZEND_DO_UCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
zend_function *fbc = call->func;
zval *ret;

if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) {
Copy link
Contributor

@TysonAndre TysonAndre Dec 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert deprecation to exception is not tested yet, and code is not adopted from internal deprecations.

Should this go after SAVE_OPLINE? I see that it's done in ZEND_DO_FCALL_BY_NAME,, and if I remember correctly, it may matter if:

  1. The set_exception_handler throws an Exception - Definitely add a unit test of this.
  2. Also, I forget how it should work, but HANDLE_EXCEPTION() may need to be called directly or indirectly to return to the caller

@@ -4074,6 +4082,10 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
ret = EX_VAR(opline->result.var);
}

if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) {
zend_deprecated_function(fbc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elsewhere, I see a much more verbose handling that does include HANDLE_EXCEPTION. I haven't checked if all of the teardown applies, but this should test that subsequent statements aren't executed (e.g. call_deprecated_function_when_error_handler_throws(); echo "Unreachable\n";

		if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) {
			zend_deprecated_function(fbc);
			if (UNEXPECTED(EG(exception) != NULL)) {
				UNDEF_RESULT();
				if (!RETURN_VALUE_USED(opline)) {
					ret = &retval;
					ZVAL_UNDEF(ret);
				}
				ZEND_VM_C_GOTO(fcall_by_name_end);
			}
		}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, i haven't copied this over because i don't fully understand it yet, but left open a todo. A test that converts deprecation into exception will probably reveal what this is used for.


ZVAL_UNDEF(&tmp);
str = zend_string_init(ZEND_STRL("message"), 1);
zend_declare_typed_property(zend_ce_deprecated_attribute, str, &tmp, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be MAY_BE_STRING or MAY_BE_STRING|MAY_BE_NULL?

Add a test of $deprecated = new Deprecated('x'); $deprecated->message = null;?

return;
}

if (Z_TYPE(message) != IS_STRING) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature in stubs has ?string $message = null - would be more consistent if this allowed explicitly passing #[Deprecated(null)] to work the same way as new Deprecated(null);

zval message;

/* As this is run in the middle of compilation, fetch the attribute value without
* specifying a scope. The class is not fully linked yet, and we may seen an
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* specifying a scope. The class is not fully linked yet, and we may seen an
* specifying a scope. The class is not fully linked yet, and we may see an

--FILE--
<?php

#[Deprecated()]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add test of Deprecated(null)?

--FILE--
<?php

error_reporting(E_ALL | E_DEPRECATED);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: As of PHP 8.0, E_ALL includes E_DEPRECATED, so this is redundant in a test targeting 8.0

zval_ptr_dtor(&message);

zend_error_noreturn(E_COMPILE_ERROR,
"Deprecated::__construct: Argument #1 ($message) must be of type string, %s given",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for the message not mentioning null

--EXPECTF--
Deprecated: Function test() is deprecated in %s

Deprecated: Function test2() is deprecated, use test() instead in %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like is deprecated. Reason: %s would be my preference for what may end up being seen in practice, such as Deprecated('inefficient'), 'will remove', 'Sentence 1. Sentence 2.', etc., but there's probably an argument that people should match deprecation messages to whatever format is expected by PHP and I'm not great at writing error messages.

Should this avoid the extra formatting for the empty string #[Deprecated('')]?

@@ -6,3 +6,8 @@ final class Attribute
{
public function __construct(int $flags = Attribute::TARGET_ALL) {}
}

final class Deprecated
Copy link

@pronskiy pronskiy Dec 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate what are the benefits of making it final?

Doesn't it make sense for similar attributes to inherit from this one? E.g. PhpStorm's deprecated attribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. For now the code uses an API that finds exact matches of Deprecated attribute usage, inheritance would require some changes. It should be possible to add them, i will look into it next time I work on the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it wasn't final, then the php compiler wouldn't be able to do this check by name and cache it in opcache without knowing what other files contained. Opcache works on a single file at a time - knowing what the parent classes of a given class are is impossible when you know only a single file's contents, so even making it emit E_DEPRECATED for a subclass would be impractical to do without hurting performance and require attempting to autoload any user-defined attribute when a file is being compiled.

It also makes it much easier to read code - you don't have to know the parent classes to tell if something would emit a deprecation notice at runtime.


Speaking of which, it may be useful to prevent class_alias where the source is a built-in attribute (which has observable effects) because the aliased classes wouldn't actually have an effect, but that's out of scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, i forgot it will require autoloading during compile, which does not really work

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TysonAndre, @beberlei, thanks for the clarification. I now understand the technical limitation.

I'm wondering if there is any other way to make the attributes compatible. Here are the ideas:

  1. Match the signatures: (string $message) --> (string $message, string $replacement)
  2. Add options: (string $message) --> (string $message, ...$options)
  3. Split PhpStorm attribute into two separate: #[Deprecated] and #[DeprecatedReplacement]

While the latter is easier for this RFC, I believe such solution is not future-proof. I mean a similar issue may arise later with other attributes introduced and it might not be always possible to split.

What do you think?

/cc @nikic

@iluuu1994
Copy link
Member

@beberlei This has been stale for a while. Are you planning to pursue this RFC at some point?

@beberlei beberlei closed this May 2, 2023
@beberlei beberlei force-pushed the DeprecatedAttribute branch from d1ed400 to 56d1cc7 Compare May 2, 2023 18:20
@beberlei beberlei mentioned this pull request May 22, 2023
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants