Psalm: Literal type reported as invalid type if there is only one

Created on 20 Nov 2019  路  9Comments  路  Source: vimeo/psalm

I am often using these types of annotations:

/**
 * @var string
 * @psalm-var "easy"|"medium"|"hard"
 */
private $type;

This enforces enum types on variables for psalm, so only valid types are used within the code base, while being backwards-compatible. Yet the following does not work currently:

/**
 * @var string
 * @psalm-var "easy"
 */
private $type;

If there is only one value, psalm reports "easy" is not a valid type. The same thing happens with other types as well (like ints, floats, etc.), for example:

/**
 * @var int
 * @psalm-var 3
 */
private $type;

This will report: 3 is not a valid type, yet something like 3|4 is correctly recognized.

Often multiple values are used in enums, but it can happen that at some point there is only one valid value (maybe more are planned in the future, but not yet), so it would be nice to be able to define this too.

enhancement

Most helpful comment

I've now enabled support for this

All 9 comments

There's an ugly workaround: you can repeat the same value, making it syntactically two-element enum: @var 3|3 $varname

why isn't that a constant?

An integer could never be a constant, and how would a literal string like "easy" be a constant?

I don't know the reason why currently it does not work for just one literal entry, but I don't see a reason why it should work with multiple when | is used and not for only one, as the rules on how to identify a type should be identical. And I guess something like 3|3 or "easy"|"easy" would be a workaround, but it makes the code less readable (somebody looking at that code might wonder if this was a mistake, and when one entry is changed the other might not be changed) and such a workaround would be there solely for psalm to not misidentify it.

An integer could never be a constant, and how would a literal string like "easy" be a constant?

I think @muglug asks why you don't put those values into constants and refer to them in docblocks, like this:

class C {
   private const ONE = 1;
   /** @var self::ONE */
   private $var = self::ONE;
}

https://psalm.dev/r/226f0f18ba

Well, more importantly if a property can only hold one value, and that value cannot change, it鈥檚 more of a constant than a property.

Using constants when there is one value might be a workaround, but it does not change the fact that psalm handles one value differently than multiple values, and that this is inconsistent and surprising.

As to why you would do that: I already mentioned that you might start out an enum list with one value, and then add more later. That is how I first encountered the problem. For example, you might do something like that in an interface (which I have):

/**
 * @psalm-return "internal"
 */
public function getType(): string;

For now that is the only permitted value, because other parts which use more types have not been implemented yet - maybe you don't even know yet which types there might be, you just know there is a type "internal", which is also a good reason not to use constants, because you are prototyping, but would like to still make use of a static analyzer to make sure nobody uses not-yet-implemented values. For me that is one of the best reasons to use a static analyzer - make things as specific as possible, as early as possible, even when you are not completely done yet.

Well, more importantly if a property can only hold one value, and that value cannot change, it鈥檚 more of a constant than a property.

Property is a narrow example, we should be talking here about types in general really. Those could be property types, parameter types, return types, types used in inheritance (like T in @extends Supertype<T>) etc. Single-valued literal types could be useful for overloading in the callmap (#2293, #2022). Narrowing return types to a single value might be useful where parent/interface signature requires you to implement some method for which your implementation always returns a single value.

IMO if there's a valid type there should be a syntax to express it as a parsable string - to use it consistently in docblocks, error messages, psalter-generated changes and so on.

I've now enabled support for this

Thanks!

Was this page helpful?
0 / 5 - 0 ratings