GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Union Types by nikic · Pull Request #1 · php/php-rfcs

Conversation

@nikic

@nikic nikic commented Sep 4, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Proposal for the addition of union types T1|T2|.... There has been a previous proposal on this topic, which has been declined.

Final RFC and Vote

(Original Rendered Proposal)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

0xBBDDBA, oleg-andreyev, asgrim, CameronHall, TomasVotruba, fbaudry, brendt, joshdifabio, theofidry, jacek-foremski, and 547 more reacted with thumbs up emoji uonick, citypipper2, AdamKyle, victorhugorch, patrickallaert, rodber, antishov, albert-agelviz, sleipi, benbor, and 49 more reacted with thumbs down emoji bronek89, byanelli, NHZEX, malukenho, shambhu384, and vudaltsov reacted with laugh emoji carusogabriel, PurHur, bronek89, Mopolo, ska-ops, oprokidnev, chadanuk, jacek-foremski, fbaudry, javiereguiluz, and 104 more reacted with hooray emoji narekps, citypipper2, fkulakov, realmocaccino, hgraca, albert-agelviz, jltxwesley, bt0r, benbor, mdubowski, and 18 more reacted with confused emoji carusogabriel, AegirLeet, kunicmarko20, rybakit, pbowyer, sstok, iluuu1994, pounard, WyriHaximus, faizanakram99, and 208 more reacted with heart emoji bronek89, carusogabriel, ska-ops, fbaudry, theofidry, jacek-foremski, azjezz, linaori, sebastiaanluca, deguif, and 82 more reacted with rocket emoji bmichotte, claudepache, lukasluecke, mtsoltan, antydemant, slince, ddrv, hirenbhut93, yngc0der, byanelli, and 2 more reacted with eyes emoji
All reactions
  • 👍 557 reactions
  • 👎 59 reactions
  • 😄 6 reactions
  • 🎉 114 reactions
  • 😕 28 reactions
  • ❤️ 218 reactions
  • 🚀 92 reactions
  • 👀 12 reactions
@nikic
Union types proposal
bf47cb7
carusogabriel
carusogabriel reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

nikic and others added 2 commits September 4, 2019 10:45
@nikic @carusogabriel
Fix typos
16a72d9
Co-Authored-By: Gabriel Caruso <[email protected]>
@nikic
Fix another typo
8839f65
@carusogabriel

carusogabriel commented Sep 4, 2019

Copy link
Copy Markdown

Discussions about the previous RFC:

  • Discussion: https://externals.io/message/92274 and https://externals.io/message/93713
  • Voting: https://externals.io/message/93953
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AegirLeet

AegirLeet commented Sep 4, 2019

Copy link
Copy Markdown

I really like it. I've encountered many situations where I wanted a method to accept multiple types while retaining type safety. The solution so far was using docblock type hints + static analysis + runtime checks. Union types on a language level would be a much nicer solution!

In addition to the nullable (?Type) and iterable types mentioned in the introduction, isn't callable another union type, since it can be either an array (with certain criteria), a string (with certain criteria) or a Closure?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Ocramius
Ocramius suggested changes Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md
For this reason, support for the `false` pseudo-type is included in this proposal. A `true` pseudo-type is *not* part of the proposal, because similar historical reasons for its necessity do not exist.

The `false` pseudo-type cannot be used as a standalone type, it can only be used as part of a union.

@Ocramius Ocramius Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I think some clarity on why true is not included as a pseudo-type is needed here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

kaznovac, mairo744, and fhferreira reacted with thumbs up emoji
All reactions
  • 👍 3 reactions

@asgrim asgrim Sep 4, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

A true pseudo-type is not part of the proposal, because similar historical reasons for its necessity do not exist.

it's already there ;)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@Ocramius Ocramius Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Fair enough. A bit weak, IMO, so if there isn't much complexity involved, I'd suggest introducing it to reduce the edge cases.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

albe, JanTvrdik, IMSoP, bwoebi, mallardduck, ptlis, TimothyBJacobs, ntzm, bmadzar, Lutacon, and 12 more reacted with thumbs up emoji mnapoli, BramRoets, and sgolemon reacted with thumbs down emoji
All reactions
  • 👍 22 reactions
  • 👎 3 reactions

@Bigbear7 Bigbear7 Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Agree that true should be included as a pseudo type. Without reading the RFC one would assume it's included. It helps documentation & tutorials since no effort is needed to explain why it's not included.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

1ma, teppokoivula, someniatko, spencerwi, spawnia, kelunik, and Obacm reacted with thumbs up emoji
All reactions
  • 👍 7 reactions

@joshdifabio joshdifabio Sep 5, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Fair enough. A bit weak, IMO, so if there isn't much complexity involved, I'd suggest introducing it to reduce the edge cases.

@mnapoli I noticed your downvote. Care to elaborate? (Personally, I value your opinion.)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mnapoli mnapoli Sep 5, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@mnapoli I noticed your downvote. Care to elaborate?

@joshdifabio it's easier to simply downvote what you find on the internet rather than write a proper argument, I went with the lazy way 😄

Nikita summed it up nicely already, I don't have a strong opinion on this and I don't think my opinion matters much here, but: false would exist for legacy reasons. true would have no real reason to exist except consistency, and I would favor aiming for what's essential over consistency or completeness.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@Crell Crell Sep 6, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The logic here is sound; my one concern is that allowing false as an explicit return type seems to indicate that it's an OK/good thing to do. I think most agree these days that returning something|false is bad design, but PHP (and sadly much of userspace) is full of that bad design.

Is there some way, perhaps just in documentation, that we could call out that returning foo|false is supported but a bad idea? Or at least avoid giving the implication that it's a good idea?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@kelunik kelunik Sep 7, 2019

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I think people should just fall back to Foo|bool for now.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

azjezz, rob006, thekid, jvasseur, 7ochem, mattacosta, adhocore, and BlackBonjour reacted with thumbs up emoji
All reactions
  • 👍 8 reactions

@claudepache claudepache Sep 7, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Especially now that the syntax for nullable type has switched to T|null, it should be made clear that not only false, but also both ?false and false|null, are unsupported.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@marcospassos marcospassos Sep 8, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Adding support for false in favor of bool without proper support for literal types helps to promote bad design decisions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Majkl578, brzuchal, thekid, jvasseur, 7ochem, mattacosta, jurchiks, bernard-ng, and devfrey reacted with thumbs up emoji
All reactions
  • 👍 9 reactions
Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Ocramius
Ocramius reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Ocramius
Ocramius reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

deleugpn
deleugpn reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

@sebastianbergmann

sebastianbergmann commented Sep 4, 2019

Copy link
Copy Markdown

Appreciate the new RFC medium.

azjezz, asgrim, perk11, brendt, deguif, carusogabriel, imliam, Anahkiasen, erusev, PurHur, and 127 more reacted with thumbs up emoji ItsShadowl, hoseinz3xs, and ruudk reacted with heart emoji
All reactions
  • 👍 137 reactions
  • ❤️ 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@akalongman

akalongman commented Sep 4, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

This feature would be great! 😍

@nikic can this feature bring performance issues?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

jderusse
jderusse reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

IMSoP
IMSoP reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md Outdated
array_filter(array $array, callable $callback, ArrayFilterFlags $flag): array;
```

Proper enums are likely a better solution to this problem space, though depending on the implementation they may not be retrofitted to existing functions for backwards-compatibility reasons.

@IMSoP IMSoP Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I would really love to see both custom types and enums, as separate language features, so hope we keep the door open to both. In particular, I am strongly of the opinion that an enum value should not be coercible to a plain type, because that leads to nonsense like MONDAY == JANUARY; as you say, that would rule out using them for existing function signatures. Custom types, meanwhile, could have more complex definitions - e.g. combining unions, intersections, constrained callables, and domains such as int{>=0,<100}.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ancarda, hikari-no-yume, dbrekelmans, rob006, brzuchal, lukasluecke, and someniatko reacted with thumbs up emoji
All reactions
  • 👍 7 reactions
enumag
enumag reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md
class B extends A {}

class Test {
public A|B $prop;

@enumag enumag Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Perhaps this should not be allowed at all? Since A type already includes B as well, using A|B type has no added value over just A.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@IMSoP IMSoP Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

As noted elsewhere in the RFC, this can't be known in the general case at compile-time, because classes A and B may not have been defined yet.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Ocramius, azjezz, morrisonlevi, sirsnyder, nikic, someniatko, jurchiks, and bernard-ng reacted with thumbs up emoji
All reactions
  • 👍 8 reactions

@muglug muglug Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Yes and type analysers can do the complaining for you (though at least in my case it'll just treat as public A $prop).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@enumag enumag Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I see, makes sense. Yeah, I'm sure PHPStan will handle this.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

someniatko reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@Majkl578 Majkl578 Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This should probably be moved to a separate section: Known problems or Limitations because while it should work as @enumag says, the engine doesn't make it possible ATM.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@IMSoP IMSoP Sep 9, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I can see there would be some value if the language was able to point this out, but there's plenty of ways of writing redundant code, so I don't see it as a "limitation" particularly. For instance, you can already write if ( $x instanceof A || $x instanceof B ), or even if ( $x instanceof A || $x instanceof A ), which is clearly redundant. A tool like PHPStan may well spot this for you, and can probably spot redundant union types as well, because it can analyse your whole project.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

brzuchal and francislavoie reacted with thumbs up emoji
All reactions
  • 👍 2 reactions
@nikic
false is a subtype of bool
cf76009
@tsantos84

tsantos84 commented Sep 4, 2019

Copy link
Copy Markdown

Type aliases is great.

MarkBaker reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nikic
Mention alternatives for handling of type coercions
cf59fa5
rybakit
rybakit reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

@hikari-no-yume

hikari-no-yume commented Sep 4, 2019

Copy link
Copy Markdown

If an RFC is to be on Github and in Markdown anyway, I wonder if it couldn't be made as a pull request to the language specification repo instead. It could be beneficial to that spec for it not to be updated later after RFCs are passed, but instead the spec update could be the RFC. Perhaps this is too radical however :)

Taluu and azjezz reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Ocramius
Ocramius approved these changes Sep 4, 2019
View reviewed changes

@Ocramius Ocramius left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

👍

The comments above are mostly tiny details, I think the proposal is very much sound and very welcome too.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

asgrim, ayanozturk, cjnstn, noximo, Slamdunk, Taluu, azjezz, GoldenCompany, muglug, JosephLeedy, and 8 more reacted with thumbs up emoji Obacm reacted with laugh emoji
All reactions
  • 👍 18 reactions
  • 😄 1 reaction
@freshp

freshp commented Sep 4, 2019

Copy link
Copy Markdown

I hope it is the right place to ask for something? Its the first RFC I read, thats why please be patient.
Excuse me too, if I misinterpreted the RFC but I dont find any example where I want to use this feature, besides nullability?

IMO many programmers are going to use it like a mixed-typehint with "strict_types = 1". (something like : ":int|float|string|bool") Also IMO PHP was getting typified, harded, easier to read and write since version 7. Thx for that, but this feature could go in another direction.

Maybe a realworld example could help me to understand, which sourcecode is getting easier to maintain or to write with this new feature?

albert-agelviz, mdubowski, apodgorbunschih, svenjungnickel, janmaennig, g105b, mattjanssen, oshindeveloper3, patrickallaert, krukru, and 4 more reacted with thumbs up emoji Taluu, ancarda, brzuchal, Girgias, francislavoie, Ocramius, fesor, axelitus, lukasluecke, azjezz, and 4 more reacted with confused emoji
All reactions
  • 👍 14 reactions
  • 😕 14 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

dbrekelmans
dbrekelmans reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md

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

### `false` pseudo-type

@dbrekelmans dbrekelmans Sep 4, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

IMO adding the false pseudo-type because of library implementation details is the wrong choice. Adding a language-level feature to cope with a historical design flaw is only adding to the pile. It's legacy as soon as it's implemented.

I absolutely understand your reasoning; I myself would prefer to know a function cannot return true. However, if typehinting |bool over |false is such an issue, it should be fixed at the root (return null instead of false) instead of adding a band-aid.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

azjezz, JosephLeedy, p810, marc-mabe, iquito, mdubowski, mattacosta, brzuchal, mattjanssen, spawnia, and 7 more reacted with thumbs up emoji kaznovac reacted with thumbs down emoji iluuu1994 and sgolemon reacted with confused emoji
All reactions
  • 👍 17 reactions
  • 👎 1 reaction
  • 😕 2 reactions

@IMSoP IMSoP Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I see what you're getting at, but rather than "legacy as soon as it's implemented" it could also be seen as a pre-cursor for a more general feature, where you can specify a union of values rather than just types. See "Literal Types" in the Future Scope section for a brief discussion of this.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

deleugpn, p810, mattleff, and nikic reacted with thumbs up emoji spawnia reacted with thumbs down emoji
All reactions
  • 👍 4 reactions
  • 👎 1 reaction

@deleugpn deleugpn Sep 4, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Due to BC constraints, changes to return type like that have a devastating potential to break uncountable (read infinite) amount of code on the internet, affecting adoption rate for the upgrade. I don't think you are wrong to say it's legacy the moment it's implemented, but it's part of coping with the 20 years baggage of PHP.
I think discussing the merits of BC breaks is not worth anybody's time at this RFC as Internals have been debating that for several months already (that I know of).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

someniatko reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@dbrekelmans dbrekelmans Sep 4, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@deleugpn I'm certainly not proposing such a BC break and I agree let's avoid the BC discussion. I'd rather leave the wound exposed (typehint |bool where false is returned) than cover it up with this pseudo-type band-aid.

@IMSoP Looking at it that way, it makes more sense. :)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

deleugpn, spawnia, and peldax reacted with thumbs up emoji
All reactions
  • 👍 3 reactions

@mattacosta mattacosta Sep 5, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The problem with false is really one of meaning as opposed to technical requirement. With all the "standard" types, developers adopt a general definition of what they represent. The value false is generally defined similarly to:

// The two values of a logical condition.
enum Boolean {
  False,
  True,
}

As we can see, false was never meant to represent an error condition much in the same way that the string "apple" or the number 1234 don't generally represent error conditions either. Including false as a pseudo-type simply perpetuates this misrepresentation.

Furthermore, I would argue that may actually be detrimental to developers as its usage in this manner typically indicates an error.

  • In the case of a function or method that should return a value, what was likely meant was null or another type similar to Rust's Option<T> or Result<T, E> which are meant to represent error conditions.
    This also covers literal types, because they are generally used to represent a subset of some other type. Since true is invalid in this context and false should not be combined with anything (its supertype does not have any other values), false can no longer represent its intended meaning, one of two values.
  • In the case of a function or method that should not return a value, the correct result should actually be a thrown error instead. Using false in this second case is especially bad, as it may also prevent static analysis from narrowing the intended type.
class Map {
  function has(key: TKey): bool {}
  function get(key: TKey): T | false {}
}

function foo(): T | null {
  $map = new Map();
  if ($map->has("bar")) {
    // Here we expect `get()` to always return `T`, however because of our poor
    // design decision to not throw an exception, the type cannot be narrowed,
    // and an error would be reported here because `T | false` != `T | null`.
    return $map->get("bar");
  }
  return null;
}

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mcneely mcneely Nov 8, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I see what you're getting at, but rather than "legacy as soon as it's implemented" it could also be seen as a pre-cursor for a more general feature, where you can specify a union of values rather than just types. See "Literal Types" in the Future Scope section for a brief discussion of this.

Maybe it would be better to leave false out of this proposal but flesh it out more as a part of literal types? If someone wants to use that paradigm, allowing type false = false would allow the end use to pull off those sort of Shenanigans without encouraging poor practices by specifically implementing them in code.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
azjezz
azjezz approved these changes Sep 4, 2019
View reviewed changes
dbrekelmans
dbrekelmans approved these changes Sep 4, 2019
View reviewed changes
sgolemon
sgolemon reviewed Sep 4, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md

### Property types

Property types are invariant, which means that types must stay the same during inheritance. However, the "same" type may be expressed in different ways. Prior to union types, one such possibility was to have two aliased classes `A` and `B`, in which case a property type may legally change from `A` to `B` or vice versa.

@sgolemon sgolemon Sep 4, 2019

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Just because I had to think this through for a moment, I'd suggest explaining why property types are invariant during inheritance. Specifically that setting a property would allow for contravariance, but that reading would only allow for covariance. The intersection of those rules is invariance.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mallardduck, alcaeus, mattleff, someniatko, Crell, imbrish, and MarkBaker reacted with thumbs up emoji
All reactions
  • 👍 7 reactions
@robbertstevens

robbertstevens commented Sep 4, 2019

Copy link
Copy Markdown

I don't if it was already asked before, but why would you want this over method overloading? If a method accepts both a string and an integer wouldn't you want to have different behaviours in the method?

In which method overloading creates a much clearer code base than a method that both handles a string and an integer.

albert-agelviz, apodgorbunschih, mattjanssen, baghayi, msarca, alexdevid, spotman, robfrawley, jbrinley, peldax, and 2 more reacted with thumbs up emoji theodorejb, fesor, theUniC, lukasluecke, and jvasseur reacted with thumbs down emoji
All reactions
  • 👍 12 reactions
  • 👎 5 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nikic
Note methods that are inherited from ReflectionTypes
256535b
@beberlei

beberlei commented Sep 4, 2019

Copy link
Copy Markdown

@robbertstevens Dan has aggregated the current state of why method overloading is probably never going to happen in PHP here: https://github.com/Danack/RfcCodex/blob/master/method_overloading.md

Ocramius, imbrish, derrabus, and Obacm reacted with thumbs up emoji mattjanssen and robbertstevens reacted with confused emoji
All reactions
  • 👍 4 reactions
  • 😕 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@msarca

msarca commented Sep 4, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There are some issues I would like to address. Union types are necessary, but I believe that some improvements must be made to this proposal.

Type aliasing

Let's look at the following example.

use Foo\bar;

function test(bar $bar) {
    // is $bar an object? 
}

If the Foo\bar type exists, then $bar is definitely an object. This is how it's been for years. But now, with the introduction of exported types, $bar could be anything. And this incertitude could prove to be a PITA in the long run.

namespace Foo;
type bar = int|float;

// Usable as \Foo\bar from elsewhere

Proposal

Introduce use type. This way we could no longer make wrong assumptions.

use Foo\bar;
use type Foo\baz;
use type int|float|string|bool as scalar; // local type

function test1(baz $baz, scalar $scalar) {
    // Don't make assumptions, check type definition.
}

function test2(bar $bar) {
    // It's an object
}

Nullable types

Nullable types should not be allowed when defining a union type.

use type ?(T1|T2|T3) as foo;
use type bar;

// What's this? Nullable nullable type?
function test(?foo $val) {
}

// Is bar nullable?
function test2(bar $val) {
   // Should I check if $val is null?
   if (is_null($val)) {
   }
}

Proposal

Keep it simple. No nullable types inside unions

use type T1|T2|T3 as foo;

// Not nullable
function test(foo $val) {
    // $val can't be null
}

// Nullable
function test2(?foo $val) {
   // I should check if $val is null
   if (is_null($val)) {
   }
}

Keep the syntax clean

There should be a single way of declaring a union type. The in place way of declaring a union type should be removed.

use type float|int as number;

// Confusing..
function sum(float|int $a, int|float $b): number{
}

// ??
function example(int|float $value, int $flag = 2|4|8|16|32, string|object $item = null): float|int|false {
}

It's hard to read, hard to follow and the syntax is super ugly.

Proposal

Keep the syntax unchanged by forcing the user to either export a union type or declare it locally with the help of use type.

use type float|int as number;

// Makes sense now..
function sum(number $a, number $b): number{
}

This way the ReflectionUnionType is no longer necessary. Only the ReflectionType needs to be changed.

class ReflectionType {
    isUnionType(): bool;
    getUnionTypes(): array;
}

Putting all together..

type scalar=int|float|string|bool; // export union type
use type int|float as number; // local union type

class Foo
{
    private number $foo; // non-nullable property
    private ?number $bar; // nullable property
    
    // non-nullable arguments & return type
    function sum(number $a, number b):number {
    }
   
    // nullable arguments and return type
    function test(?scalar $value): ?scalar {
    }
}
vudaltsov, freshp, leocavalcante, patrickallaert, sorinsarca, erikgaal, bt0r, SeinopSys, b1rdex, burrag, and 21 more reacted with thumbs up emoji Girgias, JosephLeedy, realmocaccino, francislavoie, odan, albert-agelviz, edcoreweb, nikic, fmasa, canvural, and 5 more reacted with thumbs down emoji leocavalcante, boast, and rhulka reacted with heart emoji
All reactions
  • 👍 31 reactions
  • 👎 15 reactions
  • ❤️ 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Ocramius

Ocramius commented Sep 12, 2019

Copy link
Copy Markdown

@mattacosta @kocsismate in PHP, there is a concept of "unset object properties", which matches what you want here.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nikic

nikic commented Sep 12, 2019

Copy link
Copy Markdown
Member Author

To add to what @Ocramius said, typed properties default to uninitialized, so you can simply use private ?Value $lazyProperty and check whether it's initialized in getLazyProperty().

Unfortunately, there is no easy way to distinguish between an uninitialized and a null property right now (only possible via reflection), so this still falls short in practice. But generally this would be the correct way to go about it, I think.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kocsismate

kocsismate commented Sep 12, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

@nikic Yes, I agree to you, and I already missed an easy-to-use functionality to check uninitialized properties when I wanted to lazy-load other properties which had type declarations. As far as I remember I had to downgrade type declarations to PHPDoc at the end :/ .

Speaking about unset, I use PHPStan strict rules so I didn't even consider using unset properties. But this is probably another topic which falls out of the scope of the discussion about union types. :)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@IMSoP

IMSoP commented Sep 12, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

I have always hated that typed properties added an extra "uninitialized" state, which can't be detected, and only exists in certain scenarios. I still think it was a bad design.

If you want more than one terminal value, then some kind of enum or algebraic data type is probably the ideal, and a union with specific values can emulate this a bit, e.g. string|INVALID_ENTRY|FILE_NOT_FOUND. Having both null and false allows you to write string|null|false, but you're still stuck if you want a third terminal value, or if you want a lazy boolean.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kocsismate

kocsismate commented Sep 12, 2019

Copy link
Copy Markdown
Member

I admit that my Value|null|false solution is not elegant at all, but currently this is the easiest viable option (which won't be reported by static analyzers) for such a trivial use-case I have (a builder class for unit testing). Of course, I would welcome a mean to detect uninitialized properties besides reflection or inspecting the var_dump() result :D - it would certainly eliminate the need to use my workaround.

P.S: I believe the uninitialized state is a "necessary evil", otherwise we had trouble declaring non-nullable class types for properties or we ended up in a situation where Java is in now (where classes are implicitly nullable).

someniatko reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mattacosta

mattacosta commented Sep 12, 2019

Copy link
Copy Markdown

The reason I assumed that your code could work with booleans is because if not, then the property could just as easily have been typed as T|null|bool, which would mean that once again, false does not provide anything meaningful.

All of that is irrelevant to my original question of if a restriction would be feasible though. Assuming it is, I ask because the primary argument for false, as specified in the RFC, is to support internal functions. If that argument is sound, then it would seem to be a better solution. If not, then shouldn't the RFC text be updated to include the reasoning behind including this "convenience" in actual code? There's nothing mentioning its potential impact, nor anything backing the special rules involved either.

As a side note: It also kind of sucks that the false-as-a-type discussion is in like five different places throughout this PR, some of which is hidden behind resolved change requests. Maybe the next RFC experiment could use the TC39 model of having each proposal be its own repository with actual issues, and this repo just be a directory of sorts. Of course then the repo would have to be transferred to the PHP organization at some point for preservation... so ugh.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

nikic added 5 commits September 18, 2019 15:01
@nikic
Remove obsolete note about parentheses
7829b45
@nikic
Swap sections on nullable types and false types
76736e5
@nikic
Clarify that "false|null" and friends are also not allowed
36dcf38
@nikic
Clarify that no implicit coercions to "false" occur
fc3b6a5
@nikic
Slightly expand literal types section
6b29bdf
@markushausammann

markushausammann commented Sep 19, 2019

Copy link
Copy Markdown

Please make all RFC's available on Github like this from now on!

mxpshn, krlv, Taluu, TheDoctor0, slince, carusogabriel, JulianMar, deleugpn, ifeltsweet, DakuTree, and 14 more reacted with thumbs up emoji
All reactions
  • 👍 24 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kasparsklavins

kasparsklavins commented Sep 24, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

A classical example is the strpos() family of functions, which returns int|false.

While it would be possible to model this less accurately as int|bool, this gives the false impression that the function can also return a true value, which makes this type information significantly less useful to humans and static analyzers both.

A similar argument could be said in favour of scalar types for, e.g., preg_match that returns 1|0|false.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@patrickallaert

This comment has been minimized.

Sign in to view
@qbbr

qbbr commented Oct 8, 2019

Copy link
Copy Markdown

Proposal+, GH RFC - good practice! Thx @nikic.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@BenMorel BenMorel mentioned this pull request Oct 10, 2019
Types in PHPDoc blocks brick/ftp#2
Closed
@nikic nikic mentioned this pull request Oct 21, 2019
Union types php/php-src#4838
Closed
@nikic
Update reflection output for recent master change
19a12d0
@simPod simPod mentioned this pull request Oct 28, 2019
Phpstan Level 3 webonyx/graphql-php#566
Merged
mcneely
mcneely reviewed Nov 8, 2019
View reviewed changes
Comment thread rfcs/0000-union-types-v2.md

The `null` type is supported as part of unions, such that `T1|T2|null` can be used to create a nullable union. The existing `?T` notation is considered a shorthand for the common case of `T|null`.

An earlier version of this RFC proposed to use `?(T1|T2)` for nullable union types instead, to avoid having two ways of expressing nullability in PHP. However, this notation is both rather awkward syntactically, and differs from the well-established `T1|T2|null` syntax used by phpdoc comments. The discussion feedback was overwhelmingly in favor of supporting the `T1|T2|null` notation.

@mcneely mcneely Nov 8, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Why not just ?T1|T2?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@Majkl578 Majkl578 Nov 8, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It's ambiguous syntax.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@matthieu88160 matthieu88160 Nov 8, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

What if we have more than two types, like T1|T2|T3, then it's become complicated to place ce question marks (?T1|T2?|T3? or ?T1|?T2|T3?).
The T1|T2|T3|null syntax looks more comprehensive in this case.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mcneely mcneely Nov 8, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@matthieu88160 The second question mark was outside as I was asking a question. More Types would look like this ?T1|T2|T3.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mcneely mcneely Nov 8, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@Majkl578 But is it really that ambiguous? The question mark already establishes to allow null. ?T1 which is current is the same as null|T1. Whether someone reads that as (null|T1)|T2|T3 or null|T1|T2|T3 isn't going to make a difference.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@Majkl578 Majkl578 Nov 8, 2019

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

(null|T1)|T2|T3 and null|T1|T2|T3 is equivalent.

Please read the discussion in this thread where this choice was discussed: #1 (comment)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@nikic
Add link to final RFC document
edf6ac2
@nikic nikic added the Accepted RFC accepted label Nov 8, 2019
@nikic

nikic commented Nov 8, 2019

Copy link
Copy Markdown
Member Author

Voting has closed with 61 votes in favor and 5 against. As such, the RFC is accepted.

kocsismate, rob006, iluuu1994, azjezz, bronek89, KacerCZ, oleg-andreyev, Jarzon, ammarfaizi2, skmedix, and 2 more reacted with hooray emoji ruudk, iluuu1994, bronek89, and andrevalentin reacted with heart emoji vintagesucks, bronek89, and andrevalentin reacted with rocket emoji
All reactions
  • 🎉 12 reactions
  • ❤️ 4 reactions
  • 🚀 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nikic nikic closed this Nov 8, 2019
@rask

rask commented Nov 8, 2019

Copy link
Copy Markdown

Nice!

Meta: will RFCs be opened to broader discussion via GitHub in the future as well? Also would it make sense to actually merge the accepted RFCs into the repo, instead of just closing the PR?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nikic

nikic commented Nov 8, 2019

Copy link
Copy Markdown
Member Author

@rask See https://externals.io/message/107747. The tl;dr for now is: RFCs can be initially submitted against GitHub, but have to move to the PHP wiki before voting.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kalinin-k-a

kalinin-k-a commented Nov 8, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Sad but true.
Union types satisfies the worst programming practices. It makes interfaces unpredictable, it forces developers to write a lot of if(typecheck) {} ....

Nullable types are fine. I also understand that a lot of builtin function return false if error, so something like falseable is also good. For example ?int - falsable int, ??int - nullable int.

But the most of examples I see here https://gist.github.com/nikic/64ff90c5038522606643eac1259a9dae are just funny. RFC must prohibit such thigs:
Organization|Organization[]|Person|Person[]
\PHPUnit_Framework_MockObject_MockObject|\Payum\Paypal\ExpressCheckout\Nvp\Api
string|integer|array|Zend_Date
"Enum|null|bool|int|float|string|array
Awful!

Maybe some words as "scalar", "numeric" are acceptable, but such a full freedom in types definition is a.. is a... is a bottom|disaster|php-sunset|distrubance|upset|etc

ZRayCC and mdubowski reacted with thumbs up emoji Ocramius, jvasseur, jdreesen, ammarfaizi2, deleugpn, skmedix, matthieu88160, bronek89, Majkl578, and azjezz reacted with thumbs down emoji
All reactions
  • 👍 2 reactions
  • 👎 10 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@zzz6519003

zzz6519003 commented Nov 12, 2019

Copy link
Copy Markdown

to save space?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ZRayCC

ZRayCC commented Nov 13, 2019

Copy link
Copy Markdown

It's terrible feature. I can't accept that. I like that one variable has one type.
In a team, some people will make the code worst because of the union types. It a bad standard and hardly trace.

minbaby and mdubowski reacted with thumbs up emoji matthieu88160, Ocramius, and azjezz reacted with thumbs down emoji
All reactions
  • 👍 2 reactions
  • 👎 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@php php locked as resolved and limited conversation to collaborators Nov 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

40 more reviewers
@msarca msarca msarca left review comments
@trowski trowski trowski left review comments
@TysonAndre TysonAndre TysonAndre left review comments
@kelunik kelunik kelunik left review comments
@theodorejb theodorejb theodorejb left review comments
@bwoebi bwoebi bwoebi left review comments
@claudepache claudepache claudepache left review comments
@mcneely mcneely mcneely left review comments
@joshdifabio joshdifabio joshdifabio left review comments
@Majkl578 Majkl578 Majkl578 left review comments
@rob006 rob006 rob006 left review comments
@kocsismate kocsismate kocsismate left review comments
@matthieu88160 matthieu88160 matthieu88160 left review comments
@carusogabriel carusogabriel carusogabriel left review comments
@Bigbear7 Bigbear7 Bigbear7 left review comments
@markrandall markrandall markrandall left review comments
@tuqqu tuqqu tuqqu left review comments
@lisachenko lisachenko lisachenko left review comments
@cs278 cs278 cs278 left review comments
@Ocramius Ocramius Ocramius approved these changes
@Crell Crell Crell left review comments
@asgrim asgrim asgrim left review comments
@rybakit rybakit rybakit left review comments
@enumag enumag enumag left review comments
@jderusse jderusse jderusse left review comments
@hikari-no-yume hikari-no-yume hikari-no-yume left review comments
@derrabus derrabus derrabus left review comments
@IMSoP IMSoP IMSoP left review comments
@mnapoli mnapoli mnapoli left review comments
@sgolemon sgolemon sgolemon left review comments
@marcospassos marcospassos marcospassos left review comments
@iquito iquito iquito left review comments
@mattacosta mattacosta mattacosta left review comments
@Danack Danack Danack left review comments
@gocanto gocanto gocanto approved these changes
@brzuchal brzuchal brzuchal approved these changes
@dbrekelmans dbrekelmans dbrekelmans approved these changes
@deleugpn deleugpn deleugpn approved these changes
@muglug muglug muglug approved these changes
@azjezz azjezz azjezz approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

Accepted RFC accepted

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

20 participants

@nikic @carusogabriel @AegirLeet @sebastianbergmann @akalongman @tsantos84 @hikari-no-yume @freshp @robbertstevens @beberlei @msarca @IMSoP @patrickallaert @muglug @Ocramius @mikeschinkel @oleg-andreyev @gocanto @KalleZ @dgafka
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.