Re: [PHP-DEV] [Concept] declare(strict_identifiers=1)

"[email protected]" <[email protected]>
Newsgroups gmane.comp.php.devel
Message-ID <AMBP191MB288662B28A850B892DDB56BCCEAD2@AMBP191MB2886.EURP191.PROD.OUTLOOK.COM>
 > raises the question for me of how this will interact with variable
 > variables which don't comply with the proposed rules - AFAICS those
 > wouldn't be able to be a compile time error and they also wouldn't have
 > been found in the scan of Packagist files.

Correct on both counts. Claude Pache gave me better vocabulary for the
first half than I had, and I went and measured the second half rather than
guessing at it.

PHP distinguishes names from identifiers. An identifier is a lexical token
the scanner produces from source text; a name is any string that reaches a
symbol table, and that set is far larger -- you can create a variable named
"+!" or a class alias named "" today. What I described governs identifiers
only. $$name, define(), class_alias() and property names materialised by
json_decode() are untouched. That is not a hole I carved out for
convenience; it is a line PHP already draws and already enforces
syntactically.

On your second point: you were right that my scan could not see these, so
I extended it. Names built from a run-time string remain unmeasurable by
anyone, but the statically visible subset is not -- literals in
name-creating and name-looking-up positions: ${'...'}, ->{'...'},
?->{'...'}, Foo::${'...'}, define(), constant(), class_alias(),
property_exists(), method_exists() and friends.

Across the same 4,863 packages and 520,802 files, that turns up **five**
non-ASCII names, in two packages. All five, in full:

halaxa/json-machine, src/TokensWithDebugging.php:39, under the author's own
comment "Treat UTF-8 BOM bytes as whitespace":

     ${"\xEF"} = ${"\xBB"} = ${"\xBF"} = 0;

Three variables named after the individual bytes of the UTF-8 BOM, used as
a lookup table alongside ${' '}, ${"\n"}, ${'{'} and so on. None of the
three is valid UTF-8 on its own, and none of them could be written as an
identifier at all.

rowbot/url (vendored into wp-php-toolkit/data-liberation),
tests/WhatWg/URLSearchParamsConstructorTest.php:209-210:

     $obj3->{"c\u{D83D}"} = '23';
     $obj3->{"d\u{1234}"} = 'foo';

WHATWG URL conformance test data. U+D83D is a lone high surrogate, which
PHP encodes as ED A0 BD and which is not well-formed UTF-8; U+1234 is
ordinary Ethiopic. Four of the five would fail the identifier rule, and
that is the point rather than a problem: every one of them is a
deliberate use of the name syntax precisely because it is not an
identifier.

So as far as I can measure it, the existing split is doing its job. People
who need non-identifier names reach for the name syntax, rarely and on
purpose. I would not have believed that number before running it, and I
would not have run it if you had not asked.

 > I imagine "on the fly" class creation, like when mocking code may also
 > run into issues with this up to a point ?

This one has a definite answer, which I checked rather than assumed: code
passed to eval() does not inherit strict_types from the calling file. It
is compiled as its own unit and may carry its own declare; include behaves
the same way. So under an opt-in model, PHPUnit, Mockery and Prophecy
generating classes at run time sit outside it entirely. Under a mandatory
model they would be checked, but a generated mock name derives from the
mocked class, so a non-conforming name only appears if the class being
mocked already had one.

 > Those are also the things which static analysis of code would not be able
 > to find or flag (if this were left to static analysis).

That is the strongest argument for putting any of this in the engine that
anyone has made in this thread, including me, and I do not want to claim
more from it than it gives.

A linter can never reach run-time names. But the engine as I described it
does not reach them either. The difference is that the engine could be
extended there and a linter could not -- and extending it means validating
every symbol table insertion, a cost paid on every dynamic property write,
and json_decode() throwing on untrusted input. I think that is a much
larger and probably worse proposal. It is, though, a real option rather
than an impossible one, which is more than the tooling route offers.

The scanner change and the raw output are in the repository.

Regards,
Luca
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.