Summary
When Collection::pluck($valueColumn, $keyColumn) is called on an Eloquent collection with both arguments, and both columns have known types via @property annotations on the model, the resulting ->all() is still inferred as array<array-key, mixed> instead of array<KeyType, ValueType>. This forces a LessSpecificReturnStatement on any method that declares the narrowed shape as its return type.
This is the two-argument sibling of #486 (which covered single-argument pluck('column') and is closed completed). Two-argument pluck is not narrowed today.
Repro
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property string $title
* @property string $slug
*/
class Article extends Model {}
/**
* @property string $name
*
* @method HasMany<Article, $this> articles()
*/
class Author extends Model
{
public function articles(): HasMany
{
return $this->hasMany(Article::class);
}
}
class Presenter
{
/**
* @return array<string, string>
*/
public function titlesBySlug(Author $author): array
{
return $author->articles()->pluck('title', 'slug')->all();
}
}
Psalm reports:
ERROR: LessSpecificReturnStatement
The type 'array<array-key, mixed>' is more general than the declared return type
'array<string, string>' for Presenter::titlesBySlug (see https://psalm.dev/129)
return $author->articles()->pluck('title', 'slug')->all();
Same shape applies when the collection comes from Model::query()->get()->pluck('value_col', 'key_col'), or from any Eloquent\Collection whose model declares both columns via @property.
Expected
For Collection<TKey, TModel of Model>::pluck(string \$value, string \$key):
- if
TModel has @property V \$value and @property K \$key (where K resolves to an array-key type), the return should be Support\Collection<K, V>;
- consequently
->all() should resolve to array<K, V>.
Today the plugin appears to leave both TKey and TValue unresolved, so the call chain collapses to array<array-key, mixed>.
Environment
psalm/plugin-laravel: dev-master
vimeo/psalm: 7.0.0-beta19
- PHP: 8.4
Workaround
/** @var array<string, string> $titlesBySlug */
$titlesBySlug = $author->articles()->pluck('title', 'slug')->all();
return $titlesBySlug;
…or splitting into two pluck calls and recombining, which is strictly worse.
Related
Summary
When
Collection::pluck($valueColumn, $keyColumn)is called on an Eloquent collection with both arguments, and both columns have known types via@propertyannotations on the model, the resulting->all()is still inferred asarray<array-key, mixed>instead ofarray<KeyType, ValueType>. This forces aLessSpecificReturnStatementon any method that declares the narrowed shape as its return type.This is the two-argument sibling of #486 (which covered single-argument
pluck('column')and is closed completed). Two-argumentpluckis not narrowed today.Repro
Psalm reports:
Same shape applies when the collection comes from
Model::query()->get()->pluck('value_col', 'key_col'), or from anyEloquent\Collectionwhose model declares both columns via@property.Expected
For
Collection<TKey, TModel of Model>::pluck(string \$value, string \$key):TModelhas@property V \$valueand@property K \$key(whereKresolves to anarray-keytype), the return should beSupport\Collection<K, V>;->all()should resolve toarray<K, V>.Today the plugin appears to leave both
TKeyandTValueunresolved, so the call chain collapses toarray<array-key, mixed>.Environment
psalm/plugin-laravel:dev-mastervimeo/psalm:7.0.0-beta19Workaround
…or splitting into two
pluckcalls and recombining, which is strictly worse.Related
pluck('column')value inference. The fix there did not extend to the two-argument form, so the key side is stillarray-keyand the value side falls back tomixed.