Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:116022 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 75809 invoked from network); 9 Sep 2021 03:06:35 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 9 Sep 2021 03:06:35 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 6EBD71804AA for ; Wed, 8 Sep 2021 20:44:25 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 8 Sep 2021 20:44:24 -0700 (PDT) Received: by mail-pg1-f178.google.com with SMTP id k24so443411pgh.8 for ; Wed, 08 Sep 2021 20:44:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dqxtech-net.20150623.gappssmtp.com; s=20150623; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc:content-transfer-encoding; bh=geDyrXpsgcx3LyIF9Rrbd16lY9s66y0CqHqo8g/a/vY=; b=gps9uU3htfeFRuZVl5am0Bj81fbKFCbDa7ez4lYlYS5eASsBmA9lGb/G34x1JJatYU 2S/qeXePGVWzPf4xGYcnSJUm5cBqIumKGoi47NRqlossDh++2LoYrxo9Duxm1aTujwNo kditR9+htbkU43TlBcSZCul5UW4P+sS8daZ5XaAbZzc6t1cjAwPevrVnWJOabW5/SHMq 3I+8zPM0MT5LP5n1UgKJkDFiEAUOhjyXFG8vJHxk4k5wCRxdQ7NXQFo6WKK9TzV5qwqU VFYK+SyEq5DtppxzM8/ByKKi75hMNTGRilm+CjpEP1rjgeg6KdbSwSbwUCxIaxwR+Kqr 1STg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc:content-transfer-encoding; bh=geDyrXpsgcx3LyIF9Rrbd16lY9s66y0CqHqo8g/a/vY=; b=so/3Opjz78YESN/I5TBc/6X+jK2Djd/ofup3ynxDDcFWkBuQUYqHG7XIFb1m9szSCR 1D/fOlAshuKWnsUW+yM6PiVtEbIn8IDO58J3Zp+6s1oX+68rw3xlgUfGF0r4sPGu32UX Sr6+rZT9qvR0yA5b5O98ePaHcmXZ5HTDcOPvAr5cE2738qLKws2/0diITnAZW4mMD4Bz 3nga1lme8S1xkmPG2acyxd38d7FFZoCyBS+WdXE0MfO3uNTLxgb7XYflgOkcN1BwDjT8 0PKMMchNhiBhRO6ermC9/DHbS7YucHgxsfi6CbfBSPsYEyFaCrWxcwksDkE0WUQ2foV1 QHfA== X-Gm-Message-State: AOAM530BPY0YRWDR6fvAZJ3lJesYyLXNerI+zZ1A632oOl4b63G4n82l p3G2DyFbg0BVhJfsGnmtCPJ+7xd1xLinPwSVT4ujCg== X-Google-Smtp-Source: ABdhPJwboy10aHbih0znapgOzO/ZWGiqkjfSlglpkOvJULCPJyIEyPF0ifjb00D7LS9MCge6b25rxeZdN3M3rwZyQoY= X-Received: by 2002:a63:d10b:: with SMTP id k11mr777065pgg.26.1631159060244; Wed, 08 Sep 2021 20:44:20 -0700 (PDT) MIME-Version: 1.0 References: <756e4d26-2e57-4d31-83ea-28e756c3cc42@www.fastmail.com> In-Reply-To: <756e4d26-2e57-4d31-83ea-28e756c3cc42@www.fastmail.com> Date: Thu, 9 Sep 2021 05:44:09 +0200 Message-ID: To: Larry Garfield Cc: php internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] [RFC] Never For Argument Types From: andreas@dqxtech.net (Andreas Hennings) On Sat, 14 Aug 2021 at 15:05, Larry Garfield wrote= : > > On Sat, Aug 14, 2021, at 7:48 AM, G. P. B. wrote: > > On Sat, 14 Aug 2021 at 10:55, Deleu wrote: > > > > > Hi Jordan, > > > > > > Does it make sense to explain in the RFC the difference between never= and > > > mixed in this context? The RFC vaguely mentions that never can never = be > > > used directly, but if it's limited to abstract class and interfaces, = isn't > > > that already impossible to use directly? Or does it mean that the typ= e > > > would "be allowed" on any function, but because of its intrinsic beha= vior > > > it would always fail if used in non-abstract methods? > > > > > > Another clarification I'd be interested is about dropping the type > > > declaration entirely (e.g. https://3v4l.org/a4bfs), because of covari= ance > > > (or contravariance, I never know), sub classes can completely drop ty= pe > > > declaration entirely. Will never not allow this? Why? Why not? If it = does > > > allow this, does it really differ from not having any type declaratio= n on > > > the abstract function? > > > > > > My knowledge in this area is practically null so if I'm asking stupid > > > questions that are easily explained by some blog post I'd he happy to= read > > > it. > > > > > > > never and mixed are on opposite sides of the type hierarchy. > > mixed is the top type, meaning it's the supertype of any other types, a= nd > > any other type is a subtype of mixed (excluding void which is not reall= y a > > "type" if you look at it, from my understanding, in a type theory way). > > never on the other side is the bottom type, meaning it's the subtype of= any > > other type, and any other type is a supertype of never. > > Finally a lack of type declaration is treated as mixed. > > > > Liskov's substitutions rules dictate that return types are co-variant i= .e. > > more specific, and argument types are contra-variant i.e. more general. > > This is why any return type can be replaced by never and any argument t= ype > > can have it's type dropped/changed to mixed. > > As such replacing never by mixed in an argument is totally possible as > > mixed is a wider type than never. > > > > How I personally see never as an argument type is that you require a > > mandatory argument but you leave the type constraint up to the > > implementation. > > > > A recent example which bit us in php-src/the PHP documentation is the > > interface of ArrayAccess, all of the $offset parameters have a mixed ty= pe. > > Until recently the ArrayObject/ArrayIterator had incorrect stubs [1] by > > indicating that the argument was of type int|string, which practically = is > > the case as SPL's ArrayAccess handler will throw a TypeError on differe= nt > > types, however this is done manually within the call and not when the c= all > > is made. > > Ideally the $offset parameter would be of type never such that SPL, and > > userland, can specify what type of offsets they accept, be that the usu= al > > int|string, only int for list-like objects, only string for dictionary-= like > > objects, maybe even object|int|string to allow GMP objects for arbitrar= y > > precision. > > Whereas every implementer of ArrayAccess is forced to accept mixed for = the > > $offset and need to manually enforce the type. > > > > This is the "power" of the never type as an argument type. > > > > Best regards, > > > > George P. Banyard > > > > [1] > > https://github.com/php/php-src/pull/7215/files#diff-e330df347cac9e68d2d= 07a06535c534cd8c2438a1af703cd4245b8ce91ec65afL9-R10 > > So... if I am following correctly, the idea is to allow `never` to be use= d in an interface/abstract method only, as a way to indicate "you must spec= ify a type here of some kind; I don't care what, even mixed, but you have t= o put something". Am I following? > > I'm not sure on the type theory of it, but it does feel like a hack at fi= rst blush. You don't have to specify a type. When overriding the method in a child interface or abstract class, it must have a parameter in the same place (and ideally with same name, to support named argument calls). The parameter in the overridden method may or may not have a type hint. The parameter type hint can be "never", as in the parent, but then you cannot call the method. See also this older discussion, https://externals.io/message/100275#100300 Example: interface Fruit {..} interface Apple extends Fruit {..} interface Banana extends Fruit {..} interface AbstractFruitEater { function eat(EMPTY_TYPE $fruit); } interface BananaEater extends AbstractFoodEater { function eat(Banana $banana); } interface AppleEater extends AbstractFoodEater { function eat(Apple $apple); } // In an ideal world, UniversalFruitEater would extend every other FruitEater type, but that's not really possible. interface UniversalFruitEater extends AbstractFoodEater /* , BananaEater, AppleEater */ { function eat(Fruit $fruit); } ---- Btw, I wonder what this means for optional parameters. Currently, "null" is not allowed as a parameter type hint, but it would be a natural bottom type for "?*". How narrow-minded of PHP! E.g. interface AbstractOptionalFruitEater { function eat(null $fruit); // Not allowed, but would make sense here. } interface OptionalBananaEater extends AbstractFoodEater { function eat(?Banana $banana); } interface OptionalAppleEater extends AbstractFoodEater { function eat(?Apple $apple); } ---------- I am not sure yet how useful this really is in practice, if we cannot "close the circle" and have UniversalFruitEater extend all the other FruitEater interfaces, and if we don't have generics. The first idea would be a mapper that chooses a suitable eater like so: abstract class MapperEaterBase implements UniversalFruitEater { public function eat(Fruit $fruit) { // IDE will get confused here, because it cannot verify that the eater from findSuitableEater() is actually suitable. return $this->findSuitableEater($fruit)->eat($fruit); } abstract protected function findSuitableEater(Fruit $fruit): AbstractFruitEater; } This would be a kind of userland "method overloading". The "map of eaters" could be built with reflection, or with repeated try/catch until one of them works. However, I am still undecided whether this is the best way to achieve this. I am currently building a system similar to this, but here every implementation has a method that reports which types are supported. My equivalent to the eat() method simply accepts anything, and then uses instanceof internally to reject parameters with the wrong type. The "never" type for parameters could provide some new possibilities here. -- Andreas > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >