Re: [PHP-DEV] PHP True Async RFC - Stage 2

2025-03-21 Thread Edmond Dantes
>
>  Again, that's the *how*, not the *why*. We only need to "get rid of" the
parentheses if there's some reason to type them in the first place.
>
I’ve already understood that. You mean it’s not the reason, but one of the
possible solutions.
But it’s one of the solutions that, from my point of view, causes less
surprise.
Because the alternatives like `spawn fn` and `spawn {}` seem more radical
to me compared to the current design.
In that case, perhaps `spawn_fn` or `spawn closure` would be better options.

>
> spawn ( fn() => do_something( fetch_something($input) ) )();

Now this option, I’d say, looks realistic.

> spawn => do_something( fetch_something($input) ) );

Looks good. But then, of course, it turns out that spawn plays two roles —
including defining a closure in the language. That specific point is what
makes me doubt it.
It's like the language has TWO keywords for creating a closure.

Essentially, no matter how you approach it, some compromise will have to be
made.
* Either accept potential confusion between a variable and a `function`,
* Or agree that a closure will have two forms, and everyone will have to
learn them.

If we go with the second option, then we can use both forms:

```php
spawn use {};
spawn => code;
```

>
>  Again, though, this could easily be added later when a need becomes
> visible, as long as we don't do something weird now that closes the door
> on it.
>

I tend to agree. If there's doubt, it's better to postpone it.

> spawn => $input |> fetch_something(...) |> do_something(...);

It can be made even cleaner by adding a parallelism operator.
That way, there’s no need to write `spawn` at all.

>
>  I suggest we leave this sub-thread here; there's plenty of other things
to discuss. :)
>

Ok!


Re: [PHP-DEV] Potential RFC: mb_rawurlencode() ?

2025-03-21 Thread Tim Düsterhus

Hi

Am 2025-03-18 18:48, schrieb Paul M. Jones:

$iriPath = '/heads/' . rawurlencode($val) . '/tails/');
assert($iriPath === '/heads/fü bar/tails/'; // false


From my reading of RFC 3987 that result is incorrect. The space is 
neither listed as `iunreserved`, not as `sub-delims`, thus isn't a valid 
`ipchar`. Thus the space needs to be encoded as %20 for IRIs as well. 
The same mistake applies to the reference userland implementation below.


Best regards
Tim Düsterhus


Re: [PHP-DEV] [RFC] [Discussion] Never parameters

2025-03-21 Thread Tim Düsterhus

Hi

Am 2025-03-20 21:04, schrieb Juris Evertovskis:
I'm not opposed to having a bottom type, but the `try`/`tryFrom` issue 
feels caused by another shortcoming.


In my view`from` and `tryFrom` are factory methods that construct an 
instance by being called on the class itself. As such they should not 
be subject to LSP at all, just like __construct is not.


Indeed. I said the same in the PR on GitHub: 
https://github.com/php/php-src/pull/18016#issuecomment-2715887293. 
Static methods within an interface are not useful.


The `never` type would however still be useful for non-static methods in 
interfaces and to round off the type hierarchy.


Best regards
Tim Düsterhus


Re: [PHP-DEV] Potential RFC: mb_rawurlencode() ?

2025-03-21 Thread Tim Düsterhus

Hi

Am 2025-03-20 17:46, schrieb Paul M. Jones:

```php
function mb_rawurlencode(string $string, string $encode): string {}
```


Ah yes, you're right -- probably `?string $encode = null` to match with 
mb_substr().


I am not sure if that signature makes sense and if the proposed 
functionality fits into mbstring for that reason. IRIs are defined as 
UTF-8, any other encoding results in invalid output / results that are 
not interoperable. As one example paragraph from RFC 3987:



Conversions from URIs to IRIs MUST NOT use any character encoding
other than UTF-8 in steps 3 and 4, even if it might be possible to
guess from the context that another character encoding than UTF-8 was
used in the URI.


The correct solution to me is to build a proper thought-through API as 
part of the proposed new Uri namespace and not adding new standalone 
functions without a clear vision.


Best regards
Tim Düsterhus


Re: [PHP-DEV] [RFC] [Discussion] Never parameters

2025-03-21 Thread Rowan Tommins [IMSoP]

On 21/03/2025 11:50, Tim Düsterhus wrote:

Am 2025-03-20 21:27, schrieb Matt Fonda:
If an interface adds a method but makes no promises about what 
parameters

it accepts, then why is it part of the interface in the first place--why
add a method that can't be used?


It would more cleanly allow for userland / PHPDoc-based generics, 
while still providing some engine-enforced type safety. Consider this 
example (not sure if I got the syntax completely right):


    /** @template T */
    interface Comparable {
    /** @param T $other */
    public function compareTo(never $other): int;
    }

    /** @implements Comparable */
    final class Number implements Comparable {
    public function compareTo(Number $other): int { return $this 
<=> $other; }
    } 



I think I agree with Matt on this: the interface isn't making any usable 
promises about that method.


In this example, Comparable is a kind of "abstract interface" - in order 
to actually make use of it, you need to specialise it.


Declaring that a class implements a template interface is like 
inheriting an abstract method: either you fill in the type parameter 
("class Foo implements Comparable { ... }"), or the class is also a 
template ("class Foo implements Comparable { ... }")



I don't think the language should pretend to support something that it 
doesn't - if the contract is actually enforced by a third-party tool 
reading docblocks, put the contract in a docblock:


    /**
   * @template T
   * @method compareTo(T $other): int;
   */
    interface Comparable {
    }

    /** @implements Comparable */
    final class Number implements Comparable {
    public function compareTo(Number $other): int { return $this 
<=> $other; }

    }


--
Rowan Tommins
[IMSoP]


Re: [PHP-DEV] PHP True Async RFC - Stage 2

2025-03-21 Thread Edmond Dantes
>
>  You already explicitly await all fibers spawned in the generateReport
function, you get all the data you need, any extra spawned fibers should
not interest you for the purpose of the logic of generateReport.
>

In this specific code, it only awaits the tasks it has launched itself.
So, if another function mistakenly starts a coroutine in the current
`Scope`, that coroutine will be cancelled when the scope is destroyed.

On the other hand, code that does `await $scope` assumes that the
programmer *intends* to wait for everything and understands the
implications.
This usually means that the functions being called are part of the same
module and are designed with this in mind.

As for library functions — **library functions MUST understand what they
are doing**.
If a library function creates resources indiscriminately and doesn’t clean
them up, the language cannot be held responsible.
If library functions don’t manage resource ownership properly, the language
cannot take responsibility for that either.

>
>  This is because again, the main use case listed of making sure all
fibers are done after a request is a footgun is a non-business-logic
requirement,
> an exercise in functional purity that also reduces caching and
concurrency opportunities, as mentioned before.
>

In this RFC, there is no such primary use case.
There is the `await $scope` construct, but it can no longer be used as the
default.
There used to be a `await currentScope()` construct, which was a footgun —
but it will no longer exist.

I also removed `globalScope`, because in 99% of cases it’s an anti-pattern
and can be easily replaced with code that creates a coroutine in a separate
`Scope`.
Through writing examples, this became clear.

>
>  A (somewhat bikesheeding, but this has been the vast majority of the
posts on this thread anyway) note is that await could also be made to
accept an iterable of futures, avoiding the use of Async\all combinators.
>

I considered this option — it looks nice with an array — but for some
reason, it's not implemented in any language.

And here's why.

When you allow await to work with an array, it leads to significant
complications.
Because once you support arrays, you naturally want to add more variants,
like:

   - await first
   - await any
   - await ignore
   and so on.

And with additions like await until or await limit, it becomes a very large
and complex statement.

After exploring different options, I also came to the conclusion that using
a function which returns a `Future` from parameters is more flexible and
better than having a multitude of options.

The only unresolved question is until, because it could be convenient.
But it doesn’t exist in any other language.


Re: [PHP-DEV] PHP True Async RFC - Stage 2

2025-03-21 Thread Daniil Gentili
> 
> As I write more code examples, I'm starting to get annoyed by the verbosity 
> of the `spawn in $scope` construct—especially in situations where all spawns 
> need to happen within the same context.
> 
> At the same time, in 80% of cases, it turns out that explicitly defining 
> `$scope` is the only correct approach to avoid shooting yourself in the foot. 
>  
> So, it turns out that the `spawn in $scope` construct is used far more 
> frequently than a plain `spawn`.
> 
> with async
> 
> ```php
> function generateReport(): void
> {
> try {
> 
> $scope = Scope::inherit();
> 
> async $scope {
> [$employees, $salaries, $workHours] = await Async\all([
> spawn fetchEmployees(),
> spawn fetchSalaries(),
> spawn fetchWorkHours()
> ]);
> 
> foreach ($employees as $id => $employee) {
> $salary = $salaries[$id] ?? 'N/A';
> $hours = $workHours[$id] ?? 'N/A';
> echo "{$employee['name']}: salary = $salary, hours = 
> $hours\n";
> }
> }
> 
> } catch (Exception $e) {
> echo "Failed to generate report: ", $e->getMessage(), "\n";
> }
> }
> 
> ```
> 
>  async syntax
> 
> ```php
> async  {
> 
> }
> ```
> 

I can see how you think that syntactic sugar is understandably needed for spawn 
in scope, but again, you’re still writing code that makes no sense: why do you 
care about fetchEmployees (a possible library function) not spawning any fiber?

You already explicitly await all fibers spawned in the generateReport function, 
you get all the data you need, any extra spawned fibers should not interest you 
for the purpose of the logic of generateReport.

This is because again, the main use case listed of making sure all fibers are 
done after a request is a footgun is a non-business-logic requirement, an 
exercise in functional purity that also reduces caching and concurrency 
opportunities, as mentioned before.

A (somewhat bikesheeding, but this has been the vast majority of the posts on 
this thread anyway) note is that await could also be made to accept an iterable 
of futures, avoiding the use of Async\all combinators.

Regards,
Daniil Gentili.

Re: [PHP-DEV] [RFC] [Discussion] Never parameters

2025-03-21 Thread Larry Garfield
On Thu, Mar 20, 2025, at 6:02 PM, Daniel Scherzer wrote:
> On Thu, Mar 20, 2025 at 4:00 PM Larry Garfield  wrote:
>> I have a use case for this in Serde, so would be in favor.
>> 
>> We should not block this kind of improvement on the hope of generics.  Worst 
>> case, we have this plus generics so you have options, how terrible.
>> 
>
> Would you mind sharing details of your Serde use case? It seems that 
> the BackedEnum example might not have been the best (since it is for 
> static methods) and so perhaps a userland case where this would be used 
> would help.
>
> --Daniel

Simplified example to show the thing we care about:

I have an interface Formatter, like:

interface Formatter
{
public function serializeInitialize(ClassSettings $classDef, Field 
$rootField): mixed;

public function serializeInt(mixed $runningValue, Field $field, ?int 
$next): mixed;

public function serializeFloat(mixed $runningValue, Field $field, ?float 
$next): mixed;

   // And other methods for other types
}

The $runningValue is of a type known concretely to a given implementation, but 
not at the interface level.  It's returned from serializeIntialize(), and then 
passed along to every method, recursively, as it writes out an object.

So for instance, the JSON formatter looks like this:

class JsonSerializer implements Formatter
{
 public function serializeInitialize(ClassSettings $classDef, Field 
$rootField): array
{
return ['root' => []];
}

/**
 * @param array $runningValue
 * @return array
 */
public function serializeInt(mixed $runningValue, Field $field, ?int 
$next): array
{
$runningValue[$field->serializedName] = $next;
return $runningValue;
}
}

Because JsonFormatter works by building up an array and passing it to 
json_encode(), eventually.  So $runningValue is guaranteed to always be an 
array.  I can narrow the return value to an array, but not the parameter.

The JsonStreamFormatter, however, has a stream object that it passes around 
(which wraps a file handle internally):

class JsonStreamFormatter implements Formatter
{
public function serializeInitialize(ClassSettings $classDef, Field 
$rootField): FormatterStream
{
return FormatterStream::new(fopen('php://temp/', 'wb'));
}

   /**
 * @param FormatterStream $runningValue
 */
public function serializeInt(mixed $runningValue, Field $field, ?int 
$next): FormatterStream
{
$runningValue->write((string)$next);
return $runningValue;
}
}

Again, I can narrow the return value but not the param.

To be clear, generics would absolutely be better in this case.  I'm just not 
holding my breath.

Associated Types would probably work in this case, too, since it's always 
relevant when creating a new concrete object, not when parameterizing a common 
object.  If we got that, I'd probably use that instead.

Changing the interface to use `never` instead of `mixed` would have the weakest 
guarantees of the three, since it doesn't force me to use the *same* widened 
type on serializeInt(), serializeFloat(), serializeString(), etc., even though 
it would always be the same.  But it would allow me to communicate more type 
information than I can now.

How compelling this use case is, I leave as an exercise for the reader.

--Larry Garfield


Re: [PHP-DEV] [VOTE] Optional Interfaces

2025-03-21 Thread Juris Evertovskis

On 2025-03-20 18:09, Gina P. Banyard wrote:

And another user [2] was basically suggesting my previous solution of 
adding support for type classes/runtime implementation of interfaces.


Hey,

There are two ideas -- the `$object implements Iface` that you suggested 
and the `SomeClass implements Iface` that was suggested on reddit. I see 
the instance-specific one as something that a consumer can do in their 
project if they know that the `$object` actually fits where needed. That 
would be a cool feature per se. But it seems to me it would take a lot 
of bridging code to provide such "dynamically implementing" objects if 
you're the package author.


I'll address the `SomeClass implements Iface` expression vs `class 
SomeClass implements ?Iface` hereinafter.


The `?OptionalInterface` would always be within the class definition and 
in a real-life project with PSR-4 you could check the interface 
existence by inspecting the file tree or click-throughing the name in an 
IDE.


It's not obvious to me how the runtime implementation expressions could 
be in such a discoverable spot. The expression could be anywhere in the 
project. And it will likely be inside conditionals. It would surely be 
hard to discover manually. I'm not a developer of stan tooling, but I 
expect it would complicate automated static analysis as well.


BR,
Juris

[PHP-DEV] VCS Account Request: daniels

2025-03-21 Thread Daniel Scherzer
Contributing to php-src with features and bug fixes, 
https://github.com/DanielEScherzer


Re: [PHP-DEV] VCS Account Request: daniels

2025-03-21 Thread Daniel Scherzer
Hi internals.

On Fri, Mar 21, 2025 at 3:11 PM Daniel Scherzer 
wrote:

> Contributing to php-src with features and bug fixes,
> https://github.com/DanielEScherzer


Since there wasn't much space in the form field to fill out the details,
I've been contributing to various parts of the code base, mostly around
attributes, the generated stubs, and the reflection extension, as well as
trying to clean up some of the test organization. I am the author of two
RFCs ([1], [2]), the first passed and the second is still under discussion.

If granted access I would also volunteer to take on maintaining the
reflection extension, which per `EXTENSIONS` in php-src does not currently
have a primary maintainer.

I was encouraged by cmb69 to apply for an account 6 months ago[3], and have
also privately gotten encouragement from Ilija to apply (the form said that
this should be noted in the request but there didn't seem to be space).

Let me know if there are any questions I can answer,

--Daniel Scherzer

[1] https://wiki.php.net/rfc/attributes-on-constants
[2] https://wiki.php.net/rfc/never-parameters-v2
[3] https://github.com/php/php-src/pull/15977#issuecomment-2366983138


Re: [PHP-DEV] Re: Constructor property promotion for final properties

2025-03-21 Thread Tim Düsterhus

Hi

Am 2025-03-20 21:12, schrieb Daniel Scherzer:
I recently found out that constructor property promotion cannot be 
used
for final properties. I propose that it become allowed. Thoughts? 
Would

this need an RFC, or is this minor enough to be acceptable with just a
mailing list discussion?



Given the lack of engagement, I want to make it clear: unless somebody
objects, maintainers have agreed to merge a PR implementing the feature 
in

2 weeks.

If you object, please speak up.


Can you clarify if the following would result in constructor property 
promotion or not:


class Foo {
public function __construct(
final string $bar,
) { }
}

Best regards
Tim Düsterhus


Re: [PHP-DEV] [RFC] [Discussion] Never parameters

2025-03-21 Thread Gina P. Banyard
On Thursday, 20 March 2025 at 16:57, Larry Garfield  
wrote:

> On Thu, Mar 20, 2025, at 11:24 AM, Gina P. Banyard wrote:
> 
> > As the person that had the initial discussion in R11 with Jordan [1]
> > never as a parameter type for an interface actually is not the solution
> > for "poor man generics".
> > Matthew Fonda [2] already replied to the thread pointing out the remark
> > Nikita made in the discussion of the previous RFC.
> > But importantly, going from mixed parameter type to a generic parameter
> > type is allowed and not a BC change,
> > however, going from a never parameter type to a generic parameter type
> > is a BC break.
> 
> 
> To clarify, you're saying this:
> 
> [...]
>
> Am I following that? Because just from writing that I am not sure I agree, 
> which means I may be misunderstanding. :-)

I am saying:
interface I {
  pubic function foo(never $a);
}

can ***not*** be "upgraded" to

interface I {
pubic function foo(A $a);
}

whereas it is possible to go from

interface I {
  pubic function foo(mixed $a);
}

to

interface I {
pubic function foo(A $a);
}

The implementing classes are completely irrelevant in this context.

Best regards,

Gina P. Banyard


Re: [PHP-DEV] Re: Constructor property promotion for final properties

2025-03-21 Thread Alexandru Pătrănescu
On Fri, Mar 21, 2025 at 5:20 PM Daniel Scherzer 
wrote:

> On Fri, Mar 21, 2025 at 4:07 AM Tim Düsterhus  wrote:
>
>> Can you clarify if the following would result in constructor property
>> promotion or not:
>>
>>  class Foo {
>>  public function __construct(
>>  final string $bar,
>>  ) { }
>>  }
>>
>> Best regards
>> Tim Düsterhus
>>
>
> Yes, that would result in constructor property promotion. I'll need to
> retarget the original PR for master, but at
> https://github.com/php/php-src/pull/17861 you can see in
> `Zend/tests/property_hooks/final_prop_promoted_2.phpt` a very similar test
> case.
>
>
I see. Good catch Tim.
Daniel, you don't have this exact test case there.

Initially, in 8.0, only a visibility keyword would trigger the property
promotion logic.
Later, in 8.1, also the readonly keyword would trigger this and make the
property public, without the need to mention the visibility:
https://3v4l.org/Co0gl

Now we want the same for the final keyword, to trigger the property
promotion without having a visibility keyword,
because like readonly, it would not be applicable to a parameter, but just
to a property?

-- 
Alex


[PHP-DEV] Re: [VOTE] Add get_error_handler(), get_exception_handler() functions

2025-03-21 Thread Arnaud Le Blanc
Hi,

The voting has ended.

The RFC was accepted unanimously with 28 (Yes) to 0 (No) votes. Thank
you for your participation.

Kind regards,
Arnaud

On Wed, Mar 5, 2025 at 10:50 AM Arnaud Le Blanc  wrote:
>
> Hi,
>
> I just started the vote on the "Add get_error_handler(),
> get_exception_handler() functions" RFC:
> https://wiki.php.net/rfc/get-error-exception-handler
>
> The vote will end in two weeks, on March 20th, 2025.
>
> Kind Regards,
> Arnaud


Re: [PHP-DEV] Re: Constructor property promotion for final properties

2025-03-21 Thread Daniel Scherzer
On Fri, Mar 21, 2025 at 4:07 AM Tim Düsterhus  wrote:

> Can you clarify if the following would result in constructor property
> promotion or not:
>
>  class Foo {
>  public function __construct(
>  final string $bar,
>  ) { }
>  }
>
> Best regards
> Tim Düsterhus
>

Yes, that would result in constructor property promotion. I'll need to
retarget the original PR for master, but at
https://github.com/php/php-src/pull/17861 you can see in
`Zend/tests/property_hooks/final_prop_promoted_2.phpt` a very similar test
case.

--Daniel


[PHP-DEV] Re: PHP True Async RFC - Stage 2

2025-03-21 Thread Edmond Dantes
Good day, everyone.

As I write more code examples, I'm starting to get annoyed by the verbosity
of the `spawn in $scope` construct—especially in situations where all
spawns need to happen within the same context.

At the same time, in 80% of cases, it turns out that explicitly defining
`$scope` is the only correct approach to avoid shooting yourself in the
foot.
So, it turns out that the `spawn in $scope` construct is used far more
frequently than a plain `spawn`.

I remembered an example that Larry came up with and decided to use it as
syntactic sugar.

There’s some doubt because the actual gain in characters is minimal. This
block doesn't change the logic in any way.
The convenience lies in the fact that within the block, it’s clear which
`$scope` is currently active.
However, this is more about visual organization than logical structure.

Here's what I ended up with:

### Async blocks

Consider the following code:

```php
function generateReport(): void
{
$scope = Scope::inherit();

try {
[$employees, $salaries, $workHours] = await Async\all([
spawn in $scope fetchEmployees(),
spawn in $scope fetchSalaries(),
spawn in $scope fetchWorkHours()
]);

foreach ($employees as $id => $employee) {
$salary = $salaries[$id] ?? 'N/A';
$hours = $workHours[$id] ?? 'N/A';
echo "{$employee['name']}: salary = $salary, hours = $hours\n";
}

} catch (Exception $e) {
echo "Failed to generate report: ", $e->getMessage(), "\n";
}
}
```

with async

```php
function generateReport(): void
{
try {

$scope = Scope::inherit();

async $scope {
[$employees, $salaries, $workHours] = await Async\all([
spawn fetchEmployees(),
spawn fetchSalaries(),
spawn fetchWorkHours()
]);

foreach ($employees as $id => $employee) {
$salary = $salaries[$id] ?? 'N/A';
$hours = $workHours[$id] ?? 'N/A';
echo "{$employee['name']}: salary = $salary, hours =
$hours\n";
}
}

} catch (Exception $e) {
echo "Failed to generate report: ", $e->getMessage(), "\n";
}
}

```

 async syntax

```php
async  {

}
```


Re: [PHP-DEV] Consensus on argument validation for built-in functions

2025-03-21 Thread Tim Düsterhus

Hi

Am 2025-03-20 17:00, schrieb Gina P. Banyard:
And again, ValueErrors are only ever added when it *easy* to check if 
the condition is satisfied, and it very clearly points to a programming 
error.


I still find it baffling that telling someone that the code they wrote, 
even if it is decades old, is incorrect is somehow bad.
Because the alternative is letting users have bugs in their software 
that they can ignore.


I agree with that (and Kamil, who said the same thing). Passing an 
undocumented value that does *something* is a clear programmer error 
that would also break when adding new values, which is generally 
considered a backwards compatible change. Pointing out this error as an 
actual error before it causes a silent behavioral change is a good 
thing.


The `round()` function would be a good example. PHP 8.4 both added a 
validation of the `$mode` parameter if an integer value is given and 
also added new rounding modes (just as an enum in the gold release, 
though). Before PHP 8.4 it was possible to use `round($value, 5)` by 
accident and it would be interpreted as `PHP_ROUND_HALF_UP`. Now if we 
would've added a new `const PHP_ROUND_WHATEVER = 5;` constant this code 
would silently have changed its behavior. As a user I certainly would be 
interested in finding out about this mistake. A clear ValueError is easy 
to fix in a backwards compatible way, but incorrectly rounded values can 
remain undetected for so long that it becomes impossible to fix them, 
since the stored data might already be poisoned, including all backups.


Best regards
Tim Düsterhus


Re: [PHP-DEV] Re: Constructor property promotion for final properties

2025-03-21 Thread Daniel Scherzer
On Fri, Mar 21, 2025 at 9:45 AM Alexandru Pătrănescu 
wrote:

>
> On Fri, Mar 21, 2025 at 5:20 PM Daniel Scherzer <
> daniel.e.scher...@gmail.com> wrote:
>
>> On Fri, Mar 21, 2025 at 4:07 AM Tim Düsterhus  wrote:
>>
>>> Can you clarify if the following would result in constructor property
>>> promotion or not:
>>>
>>>  class Foo {
>>>  public function __construct(
>>>  final string $bar,
>>>  ) { }
>>>  }
>>>
>>> Best regards
>>> Tim Düsterhus
>>>
>>
>> Yes, that would result in constructor property promotion. I'll need to
>> retarget the original PR for master, but at
>> https://github.com/php/php-src/pull/17861 you can see in
>> `Zend/tests/property_hooks/final_prop_promoted_2.phpt` a very similar test
>> case.
>>
>>
> I see. Good catch Tim.
> Daniel, you don't have this exact test case there.
>
> Initially, in 8.0, only a visibility keyword would trigger the property
> promotion logic.
> Later, in 8.1, also the readonly keyword would trigger this and make the
> property public, without the need to mention the visibility:
> https://3v4l.org/Co0gl
>
> Now we want the same for the final keyword, to trigger the property
> promotion without having a visibility keyword,
> because like readonly, it would not be applicable to a parameter, but just
> to a property?
>
> --
> Alex
>

Sorry, I completely missed that part. Yes, that should trigger promotion,
and if it doesn't then that is a bug in my implementation. When I retarget
the PR to master I'll also add a test case for promotion from just using
`final`.

--Daniel