| Newsgroups |
gmane.comp.php.devel |
| Message-ID |
<AMBP191MB288607C18F7D03133F6A56D4CEAE2@AMBP191MB2886.EURP191.PROD.OUTLOOK.COM> |
Hey Juris,
> To prevent errors? I must admit I don't rly understand all the terms.
> I assume it implies identifiers should be more visible/readable, right?
Not readability -- unambiguity. Three concrete things, no jargon:
1. Two identifiers that look identical on screen are currently two
different identifiers. $x and $x followed by U+00A0 NO-BREAK SPACE
are separate variables. So are "a" plus a combining diaeresis and the
single character U+00E4; both display as a-umlaut.
2. Text that is not valid UTF-8 at all is currently a valid identifier.
${"\xFF\xFE"} = 1; compiles.
3. Nobody can state what a PHP identifier is except by copying the byte
class out of the scanner. The manual does exactly that, and so does
PHP-Parser.
The declare says: in this file, an identifier is well-formed UTF-8, is a
Unicode identifier in the sense of UAX #31 (the Unicode annex that
defines this for programming languages), and is written in one canonical
spelling. Anything else is a compile error.
> Why would anything break if it's per-file?
Nothing breaks. That was bad wording on my part.
The survey answers a different question: if a maintainer adds the declare
to a file they already have, does it still compile? That is adoption
friction, not breakage. It is also a calibration check -- a rule that
rejected a lot of legitimate existing code would be the wrong rule, and I
wanted to know that before proposing it rather than after.
> Do I understand it correctly that by adding that declare to 168604 you
> would uncover a single risky identifier? Not that convincing...
That number is the cost, not the benefit, and I should have separated the
two more clearly.
The single Packagist finding is what would stop compiling: symfony/cache
declares a class whose entire name is the single byte 0xA9. That is the
whole measured adoption cost across the 250 most-installed packages.
What the rule catches can only be measured where non-ASCII identifiers
actually occur, which is not in the top Packagist packages. In the GitHub
corpus, 33 of 136 non-ASCII identifiers fail the rule and 11 contain an
invisible character. One is live code: the Alipay OpenAPI SDK has
$chrtext<U+00A0> = null;
// ...
openssl_public_encrypt($block, $chrtext<U+00A0>, $res);
with a no-break space inside the name. It works only because the typo is
consistent throughout the function. Anyone who types $chrtext normally
gets a different variable, passed by reference, that stays null. Five
vendored copies across four unrelated projects in my sample.
But you are right that these are small numbers, and I would rather say so
than dress them up. The case does not rest on the bug count.
> Would it be fair to say that the same constraints can be enforced by
> linters/cs tooling?
Partly yes, and I will concede that plainly: UTF-8 validity, NFC and
UAX #31 conformance are all statically checkable. My survey tool is
exactly such a linter, written in PHP, and it is in the repository.
Three things it cannot do.
It cannot define the language. "What is a valid PHP identifier" currently
has no answer other than "whatever bytes the scanner happened to accept",
which is why the manual, PHP-Parser, every IDE and every static analyser
separately reverse-engineer the same byte class. A declare makes it a
versioned, testable statement.
It does not travel with the code. A declare is in the source file; a lint
configuration is in someone's toolchain. The file keeps its guarantee
after composer install, and the rule also covers generated and eval'd
code that never reaches a linter.
And it cannot touch semantics. Case-insensitive symbol lookup folds ASCII
only, so Stra<U+00DF>e and STRA<U+00DF>E are the same class while
Stra<U+1E9E>e is not, and Strasse and STRASSE are. No linter can fix
that, and I do not think it can sensibly be fixed before there is a
definition of what an identifier is. I deliberately kept case folding out
of this proposal, but that is the thing underneath it.
The same objection would apply to strict_types -- static analysers check
types, so why does the engine need a per-file declaration? I do not think
the answer there was "it doesn't", though I accept the parallel is not
exact, since strict_types changes runtime behaviour and this does not.
This is the weakest point of the proposal and you found it on the first
reading. If the list's view is that specifying the rule and leaving
enforcement to tooling is the right scope, that is a smaller and possibly
better RFC, and I would rather establish that now than after writing the
patch.
Thanks for the questions.
Regards,
Luca