Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Parameter Type Widening RFC #2265

Closed
wants to merge 1 commit into from

Conversation

kelunik
Copy link
Member

@kelunik kelunik commented Jan 1, 2017

@php-pulls
Copy link

Comment on behalf of krakjoe at php.net:

labelling

@php-pulls php-pulls added the RFC label Jan 1, 2017
@kelunik kelunik force-pushed the parameter-no-type-variance branch 2 times, most recently from 16d2400 to 6dce990 Compare January 1, 2017 16:33
@kelunik kelunik changed the title Implement Parameter No Type Variance RFC Implement Parameter Type Widening RFC Jan 13, 2017
@kelunik kelunik force-pushed the parameter-no-type-variance branch 5 times, most recently from 430f972 to 2e85f21 Compare January 15, 2017 11:48
@kelunik
Copy link
Member Author

kelunik commented Jan 15, 2017

Rebase onto current master with the type changes, the test failure is unrelated.

@skie
Copy link

skie commented Jan 15, 2017

Of course, send SOLID to hell. Who is Barbara Liskov??? Some mad woman? Of course! Let's allow to break this principles and ideas!

}

if (!ZEND_TYPE_IS_SET(proto_arg_info->type)) {
/* Child defines a type, but parent doesn't, violates LSP */
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be more intuitive to move this these checks into the argument checking code, rather than have it in the code that is shared between argument and return types. It works either way because return type checking only invokes the function if both types are set, but it's a bit confusing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Dunno, it's the place where it was before, so I left it there.

Copy link

Choose a reason for hiding this comment

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

This requires decoupling into argument check and return type check, otherwise we'll be allowing widening at return types (basically adding contra-variance support), which is not desired.
Return types only support co-variance.
Argument types only support contra-variance.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll add test cases to check that it doesn't allow contravariant return types and modify as needed.

@nikic
Copy link
Member

nikic commented Jan 15, 2017

@skie If you're wondering why everyone is confused about your comment: The Liskov substitution principle explicitly allows contravariance on method argument types, which is what is being implemented here. The previous implementation was too restrictive, forbidding some cases that were perfectly valid under LSP.

@b1rdex
Copy link
Contributor

b1rdex commented Jan 16, 2017

@nikic sure, contravariance must be allowed. But how did complete removing of type hint became possible and accepted?

@b1rdex
Copy link
Contributor

b1rdex commented Jan 16, 2017

@kelunik your arguments in rfc are totally mad. Enforcing type hint on method signature is breaking change and library must define new major version in this case. Depended libraries must upgrade extending/usages code and then define new self major version. That's how it must work in semver with bc-compability in mind.
Don't know why would you change language to permit totally wrong use case.

@kelunik
Copy link
Member Author

kelunik commented Jan 16, 2017

@b1rdex No, it's not a breaking change if your code documented the accepted types before and did these checks manually.

@b1rdex
Copy link
Contributor

b1rdex commented Jan 16, 2017 via email

@kelunik
Copy link
Member Author

kelunik commented Jan 16, 2017

Please explain where it breaks.

@nikic
Copy link
Member

nikic commented Jan 16, 2017

@b1rdex It may help you to think of removing a type as specifying a (non-existing) mixed type instead, where mixed is the type semilattice supremum. As such, it is always contravariant (pretty much by definition).

@b1rdex
Copy link
Contributor

b1rdex commented Jan 16, 2017

@nikic good catch.

For anyone interested: following snippet is valid in Java 😮 tested with https://www.compilejava.net/

public class Foo {
    public Foo testParentClass(int $foo) {return new Foo();}
}
public class Bar extends Foo {
    public int testParentClass(String $foo) {return 2;}
}

@b1rdex
Copy link
Contributor

b1rdex commented Jan 16, 2017

@kelunik

Changing signature is always breaking change.
Please explain where it breaks.

in php changing type hints on public api will break extenders before this got merged :)

@nikic
Copy link
Member

nikic commented Jan 16, 2017

@b1rdex The Java case is somewhat different, because it's not overriding the method, but adding an overload with a different signature. In languages with method overloading variance considerations usually only come in for generic parameters and return types.

@b1rdex
Copy link
Contributor

b1rdex commented Jan 16, 2017

@nikic shame on me, I totally forgot about polymorphism… Hope php will see it someday.

@kelunik
Copy link
Member Author

kelunik commented Jan 16, 2017

@b1rdex I don't think method overloading is something we'll see in PHP, at least not in the near future. It's rather complicated with weak types and comes with significant overhead IIRC.

in php changing type hints on public api will break extenders before this got merged :)

  1. Changing a type declaration in a subclass will still not be allowed. The only thing that's allowed is the parent having a restriction and the child not having one, thus accepting the same thing the parent accepted and more.
  2. Being able to upgrade existing type hints in docblocks + manual exceptions to use type declarations without breaking backwards compatibility is an important part to move forward with stricter typing.
  3. It didn't break before, it "just" emitted a warning, because PHP can't verify co- / contravariance at compile time currently, because type declarations do not invoke the autoloader. And we don't have method overloading. So all we can do is allowing co- / contravariance instead of method overloading.

@artemzakholodilo
Copy link

artemzakholodilo commented Feb 2, 2017

`abstract class Person
{
private $name;

public function setName($name)
{
    $this->name = $name;
}

public function getName()
{
    return $this->name;
}

protected function __toString()
{
    
}

}

class Foo
{
public function sayHi(Person $person)
{
echo "Hi " . $person->getName();
}
}

class Human extends Person
{}

$foo = new Foo();
$foo->sayHi('Artem');
`

What about this?

@schnittstabil
Copy link

schnittstabil commented Feb 2, 2017

@artemzakholodilo This PR won't affect your example; $foo->sayHi('Artem'); stills fails, because 'Artem' is not a Person instance:

Warning: The magic method __toString() must have public visibility and cannot be static […]

Fatal error: Uncaught TypeError:
Argument 1 passed to Foo::sayHi() must be an instance of Person, string given, […]

Stack trace:
#0 […].php(34): Foo->sayHi('Artem')
#1 {main}
  thrown in […]

@kelunik
Copy link
Member Author

kelunik commented Feb 3, 2017

@guilhermeblanco @nikic I've improved the implementation to use a separate function for args that does the additional checks. I've added an assertion to the current function, so it's only called if both types are set.

if (!ZEND_TYPE_IS_SET(proto_arg_info->type)) {
/* Child defines a type, but parent doesn't, violates LSP */
return 0;
}
Copy link
Member

@nikic nikic Feb 3, 2017

Choose a reason for hiding this comment

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

nit: Some space (instead of tab) indents in the above blocks

Copy link
Member Author

Choose a reason for hiding this comment

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

Should I put them into a separate commit? The rest of the file already has trimmed whitespace on empty lines.

Copy link
Member

Choose a reason for hiding this comment

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

As this is newly introduced code, it should be in the same commit.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, misunderstanding. I was referring to use of 4-space indentation instead of tabs in the lines above, not the the removal of trailing whitespace, that's fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, right, fixed. I didn't fix the other places using spaces that are not new code.

@nikic nikic self-assigned this Feb 3, 2017
/* Only one has a type declaration and the other one doesn't */
return 0;
}
ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_arg_info->type) && ZEND_TYPE_IS_SET(fe_arg_info->type));
Copy link
Member

Choose a reason for hiding this comment

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

must be proto_arg_info->type once

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, fixed.

@nikic
Copy link
Member

nikic commented Feb 4, 2017

Merged via 2edc3cf with an UPGRADING note. Thanks!

@JuJuDropThor
Copy link

Are you mad ? Why do you want to violate SOLID and Liskus Substitution like that ? Do you really think that kind of thing will improve the reputation of PHP ?

Shame.

@kelunik kelunik deleted the parameter-no-type-variance branch May 22, 2017 07:12
@kelunik
Copy link
Member Author

kelunik commented May 22, 2017

@JuJuDropThor This does not violate the Liskov substitution principle (LSP).

@JuJuDropThor
Copy link

So could you tell me, please, how that could be possible if you change declaration of your method between a type and a subtype ?

@kelunik
Copy link
Member Author

kelunik commented May 22, 2017

Sure, it's called contravariance for method argument types: https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Contravariant_method_argument_type

@JuJuDropThor
Copy link

Oh array covariance, great...

You have an array parameter:
public function foo(array $foo)

In the subclass, you could have... everything you want.
public function foo($foo)

Your subclass EverythingClass can't be replaced by an object of type ArrayClass without violating LSP.

First line of the wiki you linked:
Many programming language type systems support subtyping. For instance, if Cat is subtype of Animal, then an expression of type Cat can be used whenever an expression of type Animal could. Variance refers to how subtyping between more complex types (list of Cats versus list of Animals, function returning Cat versus function returning Animal, ...) relates to subtyping between their components.

Well, do as you wish, that RFC is validated anyway. That kind of thing doesn't honor PHP repuration.

@pmmaga
Copy link
Contributor

pmmaga commented May 22, 2017

In this case the contravariance is not the example you mentioned but exactly the other way around. Assuming your EverythngClass, ArrayClass example, and that ArrayClass is the parent and EverythingClass is the child, EverythingClass can replace ArrayClass without breaking LSP. In the example you are giving, you are trying to use the parent to replace the child which is not valid.

@kelunik
Copy link
Member Author

kelunik commented May 22, 2017

Right, an EverythingClass can't be replaced with an ArrayClass, because that's the wrong way round. EverythingClass is the subtype, not the parent type. Anything that accepts an ArrayClass also works perfectly fine with an EverythingClass, because EverythingClass handles arrays just fine, but also everything else.

@php php locked and limited conversation to collaborators May 22, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet