Git Product home page Git Product logo

Comments (7)

psalm-github-bot avatar psalm-github-bot commented on September 26, 2024

I found these snippets:

https://psalm.dev/r/307068e874
<?php

abstract class Base
{
    /**
     * @psalm-assert-if-true A $this
     * @psalm-assert-if-false B $this
     */
	abstract function isA(): bool;
    
    /**
     * @psalm-assert-if-true B $this
     * @psalm-assert-if-false A $this
     */
    abstract function isB(): bool;
}

class A extends Base
{
    function isA(): true { return true; }
    function isB(): false { return false; }
}

class B extends Base
{
    function isA(): false { return false; }
    function isB(): true { return true; }
}

function example(Base $obj): A|B
{
    if ($obj->isA()) {
        return $obj;
    } else {
        return $obj;
    }
}

function example2(Base $obj): A|B
{
    if ($obj->isB()) {
        return $obj;
    } else {
        return $obj;
    }
}
Psalm output (using commit ef3b018):

INFO: LessSpecificReturnStatement - 35:16 - The type 'Base' is more general than the declared return type 'A|B' for example

INFO: MoreSpecificReturnType - 30:30 - The declared return type 'A|B' for example is more specific than the inferred return type 'Base'

INFO: LessSpecificReturnStatement - 44:16 - The type 'Base' is more general than the declared return type 'A|B' for example2

INFO: MoreSpecificReturnType - 39:31 - The declared return type 'A|B' for example2 is more specific than the inferred return type 'Base'
https://psalm.dev/r/7a76f6d64e
<?php

/**
 * @psalm-assert-if-true 'A' $value
 * @psalm-assert-if-false 'B' $value
 */
function isA(string $value): bool
{
    return $value === 'A';
}

/**
 * @psalm-assert-if-true 'B' $value
 * @psalm-assert-if-false 'A' $value
 */
function isB(string $value): bool
{
    return $value === 'B';
}

/** @return 'A'|'B' */
function example(string $value): string
{
    if (isA($value)) {
        return $value;
    } else {
        return $value;
    }
}

/** @return 'A'|'B' */
function example2(string $value): string
{
    if (isB($value)) {
        return $value;
    } else {
        return $value;
    }
}
Psalm output (using commit ef3b018):

INFO: LessSpecificReturnStatement - 27:16 - The type 'string' is more general than the declared return type ''A'|'B'' for example

INFO: MoreSpecificReturnType - 21:13 - The declared return type ''A'|'B'' for example is more specific than the inferred return type 'string'

INFO: LessSpecificReturnStatement - 37:16 - The type 'string' is more general than the declared return type ''A'|'B'' for example2

INFO: MoreSpecificReturnType - 31:13 - The declared return type ''A'|'B'' for example2 is more specific than the inferred return type 'string'

from psalm.

kkmuffme avatar kkmuffme commented on September 26, 2024

This is not a bug.

If you return false it can be any class that extends Base not just B.
When adding a class C you can see the problem: https://psalm.dev/r/eb47b05687

I guess there should be an error added to not allow true + false assertions if BOTH of them use a class, so this is reported in line 6/7 and 12/13 already.

from psalm.

psalm-github-bot avatar psalm-github-bot commented on September 26, 2024

I found these snippets:

https://psalm.dev/r/eb47b05687
<?php

abstract class Base
{
    /**
     * @psalm-assert-if-true A $this
     * @psalm-assert-if-false B $this
     */
	abstract function isA(): bool;
    
    /**
     * @psalm-assert-if-true B $this
     * @psalm-assert-if-false A $this
     */
    abstract function isB(): bool;
}

class A extends Base
{
    function isA(): true { return true; }
    function isB(): false { return false; }
}

class B extends Base
{
    function isA(): false { return false; }
    function isB(): true { return true; }
}

class C extends Base {
    function isA(): false { return false; }
    function isB(): false { return false; }
}

function example(Base $obj): A|B
{
    if ($obj->isA()) {
        return $obj;
    } else {
        return $obj;
    }
}

function example2(Base $obj): A|B
{
    if ($obj->isB()) {
        return $obj;
    } else {
        return $obj;
    }
}
Psalm output (using commit ef3b018):

INFO: LessSpecificReturnStatement - 40:16 - The type 'Base' is more general than the declared return type 'A|B' for example

INFO: MoreSpecificReturnType - 35:30 - The declared return type 'A|B' for example is more specific than the inferred return type 'Base'

INFO: LessSpecificReturnStatement - 49:16 - The type 'Base' is more general than the declared return type 'A|B' for example2

INFO: MoreSpecificReturnType - 44:31 - The declared return type 'A|B' for example2 is more specific than the inferred return type 'Base'

from psalm.

Shira-3749 avatar Shira-3749 commented on September 26, 2024

These are just examples - I think it's up to the method to implement its own assertions correctly.

I can use final methods: https://psalm.dev/r/41b44c332d
Or assertions: https://psalm.dev/r/342dd21302

It doesn't work with enums either (enums are always final): https://psalm.dev/r/dd4afda62e (this works in PHPStan)

from psalm.

psalm-github-bot avatar psalm-github-bot commented on September 26, 2024

I found these snippets:

https://psalm.dev/r/41b44c332d
<?php

abstract class Base
{
    /**
     * @psalm-assert-if-true A $this
     * @psalm-assert-if-false B $this
     */
	final function isA(): bool
    {
        return match (true) {
            $this instanceof A => true,
            $this instanceof B => false,
            default => throw new \LogicException(),
        };
    }
    
    /**
     * @psalm-assert-if-true B $this
     * @psalm-assert-if-false A $this
     */
    final function isB(): bool
    {
        return match (true) {
            $this instanceof A => false,
            $this instanceof B => true,
            default => throw new \LogicException(),
        };
    }
}

class A extends Base
{
}

class B extends Base
{
}

class C extends Base {
    function isA(): false { return false; }
    function isB(): false { return false; }
}

function example(Base $obj): A|B
{
    if ($obj->isA()) {
        return $obj;
    } else {
        return $obj;
    }
}

function example2(Base $obj): A|B
{
    if ($obj->isB()) {
        return $obj;
    } else {
        return $obj;
    }
}
Psalm output (using commit ef3b018):

INFO: LessSpecificReturnStatement - 50:16 - The type 'Base' is more general than the declared return type 'A|B' for example

INFO: MoreSpecificReturnType - 45:30 - The declared return type 'A|B' for example is more specific than the inferred return type 'Base'

INFO: LessSpecificReturnStatement - 59:16 - The type 'Base' is more general than the declared return type 'A|B' for example2

INFO: MoreSpecificReturnType - 54:31 - The declared return type 'A|B' for example2 is more specific than the inferred return type 'Base'

ERROR: MethodSignatureMismatch - 41:5 - Method Base::isA is declared final and cannot be overridden

ERROR: MethodSignatureMismatch - 42:5 - Method Base::isB is declared final and cannot be overridden
https://psalm.dev/r/342dd21302
<?php

/**
 * @psalm-assert-if-true 'A' $value
 * @psalm-assert-if-false 'B' $value
 */
function isA(string $value): bool
{
    assert($value === 'A' || $value === 'B');
    
    return $value === 'A';
}

/**
 * @psalm-assert-if-true 'B' $value
 * @psalm-assert-if-false 'A' $value
 */
function isB(string $value): bool
{
    assert($value === 'A' || $value === 'B');
    
    return $value === 'B';
}

/** @return 'A'|'B' */
function example(string $value): string
{
    if (isA($value)) {
        return $value;
    } else {
        return $value;
    }
}

/** @return 'A'|'B' */
function example2(string $value): string
{
    if (isB($value)) {
        return $value;
    } else {
        return $value;
    }
}
Psalm output (using commit ef3b018):

INFO: LessSpecificReturnStatement - 31:16 - The type 'string' is more general than the declared return type ''A'|'B'' for example

INFO: MoreSpecificReturnType - 25:13 - The declared return type ''A'|'B'' for example is more specific than the inferred return type 'string'

INFO: LessSpecificReturnStatement - 41:16 - The type 'string' is more general than the declared return type ''A'|'B'' for example2

INFO: MoreSpecificReturnType - 35:13 - The declared return type ''A'|'B'' for example2 is more specific than the inferred return type 'string'
https://psalm.dev/r/dd4afda62e
<?php

enum Example
{
    case A;
    case B;
    
    /**
     * @psalm-assert-if-true Example::A $this
     * @psalm-assert-if-false Example::B $this
     */
	function isA(): bool
    {
        return $this === self::A;
    }
    
    /**
     * @psalm-assert-if-true Example::B $this
     * @psalm-assert-if-false Example::A $this
     */
    function isB(): bool
    {
        return $this === self::B;
    }
}

function example(Example $obj): void
{
    if ($obj->isA()) {
        /** @psalm-trace $obj */;
    } else {
        /** @psalm-trace $obj */;
    }
}

function example2(Example $obj): void
{
    if ($obj->isB()) {
        /** @psalm-trace $obj */;
    } else {
        /** @psalm-trace $obj */;
    }
}
Psalm output (using commit ef3b018):

ERROR: TypeDoesNotContainType - 29:9 - Type enum(Example::A) for enum(Example::A) is always !enum(Example::B)

INFO: Trace - 30:33 - $obj: never

INFO: Trace - 32:33 - $obj: enum(Example::B)

ERROR: TypeDoesNotContainType - 38:9 - Type enum(Example::B) for enum(Example::B) is always !enum(Example::A)

INFO: Trace - 39:33 - $obj: never

INFO: Trace - 41:33 - $obj: enum(Example::A)

from psalm.

Shira-3749 avatar Shira-3749 commented on September 26, 2024

It seems to work when combined with @psalm-this-out A|B:
https://psalm.dev/r/3efc302338

I'm not sure if this workaround should be necessary however.

Edit: It also seems to break eventually anyway: https://psalm.dev/r/9ea25aa6e3

from psalm.

psalm-github-bot avatar psalm-github-bot commented on September 26, 2024

I found these snippets:

https://psalm.dev/r/3efc302338
<?php

abstract class Base
{
    /**
     * @psalm-assert-if-true A $this
     * @psalm-assert-if-false B $this
     * @psalm-this-out A|B
     */
	final function isA(): bool
    {
        return match (true) {
            $this instanceof A => true,
            $this instanceof B => false,
            default => throw new \LogicException(),
        };
    }
    
    /**
     * @psalm-assert-if-true B $this
     * @psalm-assert-if-false A $this
     * @psalm-this-out A|B
     */
    final function isB(): bool
    {
        return match (true) {
            $this instanceof A => false,
            $this instanceof B => true,
            default => throw new \LogicException(),
        };
    }
}

class A extends Base
{
}

class B extends Base
{
}

function example(Base $obj): A|B
{
    if ($obj->isA()) {
        return $obj;
    } else {
        return $obj;
    }
}

function example2(Base $obj): A|B
{
    if ($obj->isB()) {
        return $obj;
    } else {
        return $obj;
    }
}
Psalm output (using commit ef3b018):

No issues!
https://psalm.dev/r/9ea25aa6e3
<?php

/**
 * @template T
 */
abstract class Maybe
{
    /**
     * @psalm-assert-if-true Some<T> $this
     * @psalm-assert-if-false None $this
     * @psalm-this-out Some<T>|None
     */
	final function isSome(): bool
    {
        return match (true) {
            $this instanceof Some => true,
            $this instanceof None => false,
            default => throw new \LogicException(),
        };
    }
    
    /**
     * @psalm-assert-if-true None $this
     * @psalm-assert-if-false Some<T> $this
     * @psalm-this-out Some<T>|None
     */
    final function isNone(): bool
    {
        return match (true) {
            $this instanceof Some => false,
            $this instanceof None => true,
            default => throw new \LogicException(),
        };
    }
}

/**
 * @template T
 * @extends Maybe<T>
 */
class Some extends Maybe
{
}

/**
 * @extends Maybe<never>
 */
class None extends Maybe
{
}

/**
 * @param Maybe<string> $maybe
 * @return Some<string>|None
 */
function example(Maybe $maybe): Some|None
{
    if ($maybe->isSome()) {
        return $maybe;
    } else {
        return $maybe;
    }
}

/**
 * @param Maybe<string> $maybe
 * @return Some<string>|None
 */
function example2(Maybe $maybe): Some|None
{
    if ($maybe->isNone()) {
        return $maybe;
    } else {
        return $maybe;
    }
}

/**
 * @param Maybe<string> $maybe
 * @return Some<string>|None
 */
function example3(Maybe $maybe): Some|None
{
    if ($maybe->isSome()) {
        /** @psalm-trace $maybe */;
    } else {
        /** @psalm-trace $maybe */;
    }
    
    if ($maybe->isNone()) {
        /** @psalm-trace $maybe */;
    } else {
        /** @psalm-trace $maybe */; // Some<never>, but should be Some<string>
    }
    
    return $maybe;
}
Psalm output (using commit ef3b018):

INFO: Trace - 85:35 - $maybe: Some<string>

INFO: Trace - 87:35 - $maybe: None

INFO: Trace - 91:35 - $maybe: None

INFO: Trace - 93:35 - $maybe: Some<never>

from psalm.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.