-
Notifications
You must be signed in to change notification settings - Fork 662
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
WIP: Introduce @psalm-use to import types #2924
Conversation
That is a proposal of how we could import types defined in other classes, so we don't need to repeat the types over and over again. That could be really interesting for composition of types as well. /** @psalm-type TPhone = array{phone: string} */ class Phone { /** @psalm-return TPhone */ toArray(): array {} } /** @psalm-type TName = array{name: string} */ class Name { /** @psalm-return TName */ toArray(): array {} } /** * @psalm-use TPhone from Phone * @psalm-use TName from Name * * @psalm-type TUserArray = TPhone&TName */ class User { } Signed-off-by: Jefersson Nathan <malukenho.dev@gmail.com>
The specific syntax doesn't make sense though, as typedefs are scoped to files (see #2332 (comment)). |
I think it has to be attached per class however otherwise autoloading won't work seamlessly. If we reach a type that hasn't been defined, we know what class it's defined on and can autoload the file to get the definitions. |
Please make this work, would be very useful. My team does not understand how Psalm is supposed to be used at any scale if you can't define types once and use them throughout your project. Can someone point me in the right direction on this? |
Not sure if that's just a lack of imagination on your part. Psalm is used at scale in a bunch of places. |
Psalm has two stages - scanning (parsing all the files, collecting information) and analysis (finding bugs). You can think of the scanning step as performing static reflection – i.e. "what does this method return". That static reflection depends on every docblock having a type that can be easily derived without having to reference any other file. Type aliases (in the form of already-supported If we add this layer of indirection for types, there are two options.
|
As for syntax, I think you'd want something like
as For the late-resolved type definition option: You'd also need to collect these This is not a trivial task – it's a bit of a big undertaking to get it working reasonably well. It seems like the main benefit would be for code that has a lot of array shape annotations. It's always been my position that DTOs much less messy than large array shapes, but I also appreciate that some just want to be able to describe their current codebase without having to copy-paste these large docblocks. Given the 👍 support for this, hopefully someone can start on an implementation. I'm happy to give pointers (like I've given here) but don't want to have to write a lot of code for this. Good luck! |
Thank you for the feedback @muglug! I'll continue with a real implementation for this proposal. |
Awesome! I look forward to it! |
@malukenho I've added a I've also added a |
Ok, I've also added docblock parsing (in theory at least) for The only remaining part for you now, I think, is to add support in the TypeExpander for |
Ok so I went ahead and added most of the necessary functionality, such that 0086eb2 works. There are bunch of possible edge cases though – currently only the optimistic case is supported (where the class and type alias both exist). If someone could add code to throw |
@muglug Thank you for responding to this!
You're totally correct, and I apologies for my comment coming off the wrong way.
You nailed it! In our case we have a Typescript client with many deeply nested data types, and now we're adding a PHP service that will need to deal with (both take and return) these same types. Here's an example: ['status' => array(
'type' => 'varchar',
'length' => '50',
'required' => true,
'is_enum' => true,
'enum_options' => [
['paid', 'Paid'],
['unpaid', 'Unpaid'],
['rejected', 'Rejected'],
],
'display_name' => 'Status',
'form_options' => [
'new' => [
'show' => true,
'validation_type' => ['string', 'required', 'email']
]
],
'show_list_table_column' => true
)
] I'm simply not clear on the best way to currently "feed this shape" to the type system, and reuse it project wide as the input and output of many functions. Would you instead use classes create a class for every 'layer' of nesting? To reiterate, this is an issue with my lack of understanding and an attempt at learning. 1000 apologies for the ignorant questions, and I do hope that my journey into Psalm is representative of some non-neglible group of developers and this will help solve their speedbumps in the future. |
@mholubowski thanks, I appreciate it. @malukenho I'm going to close this now, as the functionality you've suggested in this PR now exists in Psalm master: https://psalm.dev/r/157a5e9fbb. It's still not entirely fleshed out – there are likely bugs I've missed – but it should be enough for you and others to play with in advance of the release. |
I found these snippets: https://psalm.dev/r/157a5e9fbb<?php
/** @psalm-type TPhone = array{phone: string} */
class Phone {
/** @psalm-return TPhone */
public function toArray(): array {
return ["phone" => "Nokia"];
}
}
/** @psalm-type TName = array{name: string} */
class Name {
/** @psalm-return TName */
function toArray(): array {
return ["name" => "Matt"];
}
}
/**
* @psalm-import-type TPhone from Phone
* @psalm-import-type TName from Name
*
* @psalm-type TUserArray = TPhone&TName
*/
class User {
/** @psalm-return TUserArray */
function toArray(): array {
return array_merge(
(new Name)->toArray(),
(new Phone)->toArray()
);
}
}
|
@muglug I was planning to work on it this weekend. But I can at least try it out. You're awesome! Thank you for your work. |
Yeah if you can add tests and failure-handling that’d be awesome |
The implementation is very hacky, just to test what I want to achieve here.
That is a proposal of how we could import types defined in other
classes, so we don't need to repeat the types over and over again.
That could be interesting when composing types as well.