Skip to content

Commit

Permalink
Fix properties-of on generics&intersections
Browse files Browse the repository at this point in the history
  • Loading branch information
danog committed Oct 3, 2022
1 parent 3abd0b9 commit 55e6804
Show file tree
Hide file tree
Showing 62 changed files with 1,507 additions and 391 deletions.
13 changes: 10 additions & 3 deletions psalm-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="dev-master@d7e897b0eb65539a8b769a911efdf49925dd4e7d">
<files psalm-version="dev-master@2488c523963efcc6d8a1a6a8874d08bd0d7a40cf">
<file src="examples/TemplateChecker.php">
<PossiblyUndefinedIntArrayOffset occurrences="2">
<code>$comment_block-&gt;tags['variablesfrom'][0]</code>
Expand Down Expand Up @@ -331,9 +331,16 @@
<PossiblyUndefinedIntArrayOffset occurrences="1">
<code>$this-&gt;type_params[1]</code>
</PossiblyUndefinedIntArrayOffset>
<PossiblyUnusedMethod occurrences="3">
<code>replaceTypeParams</code>
<code>replaceTypeParams</code>
<code>replaceTypeParams</code>
</PossiblyUnusedMethod>
</file>
<file src="src/Psalm/Type/Atomic/TTemplatePropertiesOf.php">
<PropertyTypeCoercion occurrences="1"/>
<file src="src/Psalm/Type/Atomic/TTemplateParam.php">
<PossiblyUnusedMethod occurrences="1">
<code>replaceAs</code>
</PossiblyUnusedMethod>
</file>
<file src="src/Psalm/Type/MutableUnion.php">
<PossiblyUnusedProperty occurrences="6">
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Expand Up @@ -758,7 +758,7 @@ public static function addContextProperties(

// Get actual types used for templates (to support @template-covariant)
$template_standins = new TemplateResult($lower_bounds, []);
TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
$guide_property_type,
$template_standins,
$codebase,
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php
Expand Up @@ -1837,7 +1837,7 @@ private function getFunctionInformation(
if ($this->storage instanceof MethodStorage && $this->storage->if_this_is_type) {
$template_result = new TemplateResult($this->getTemplateTypeMap() ?? [], []);

TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
new Union([$this_object_type]),
$template_result,
$codebase,
Expand Down
Expand Up @@ -273,7 +273,7 @@ static function (string $fq_catch_class) use ($codebase): TNamedObject {
&& $codebase->interfaceExists($fq_catch_class)
&& !$codebase->interfaceExtends($fq_catch_class, 'Throwable')
) {
$catch_class_type->addIntersectionType(new TNamedObject('Throwable'));
return $catch_class_type->addIntersectionType(new TNamedObject('Throwable'));
}

return $catch_class_type;
Expand Down
Expand Up @@ -268,7 +268,7 @@ public static function analyze(
if (null !== $inferred_arg_type && null !== $template_result && null !== $param && null !== $param->type) {
$codebase = $statements_analyzer->getCodebase();

TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
clone $param->type,
$template_result,
$codebase,
Expand Down Expand Up @@ -308,19 +308,6 @@ private static function handleArrayMapFilterArrayArg(
): void {
$codebase = $statements_analyzer->getCodebase();

$generic_param_type = new Union([
new TArray([
Type::getArrayKey(),
new Union([
new TTemplateParam(
'ArrayValue' . $argument_offset,
Type::getMixed(),
$method_id
)
])
])
]);

$template_types = ['ArrayValue' . $argument_offset => [$method_id => Type::getMixed()]];

$replace_template_result = new TemplateResult(
Expand All @@ -330,8 +317,19 @@ private static function handleArrayMapFilterArrayArg(

$existing_type = $statements_analyzer->node_data->getType($arg->value);

TemplateStandinTypeReplacer::replace(
$generic_param_type,
TemplateStandinTypeReplacer::fillTemplateResult(
new Union([
new TArray([
Type::getArrayKey(),
new Union([
new TTemplateParam(
'ArrayValue' . $argument_offset,
Type::getMixed(),
$method_id
)
])
])
]),
$replace_template_result,
$codebase,
$statements_analyzer,
Expand Down Expand Up @@ -515,7 +513,7 @@ private static function handleHighOrderFuncCallArg(
$actual_func_param->type->getTemplateTypes() &&
isset($container_hof_atomic->params[$offset])
) {
TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
clone $actual_func_param->type,
$high_order_template_result,
$codebase,
Expand Down Expand Up @@ -769,7 +767,7 @@ public static function checkArgumentsMatch(
}
}

if ($function_params) {
if ($function_params && !$is_variadic) {
foreach ($function_params as $function_param) {
$is_variadic = $is_variadic || $function_param->is_variadic;
}
Expand Down Expand Up @@ -1616,7 +1614,7 @@ private static function getProvisionalTemplateResultForFunctionLike(
$calling_class_storage->final ?? false
);

TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
$fleshed_out_param_type,
$template_result,
$codebase,
Expand Down Expand Up @@ -1796,7 +1794,7 @@ private static function checkArgCount(
$default_type = new Union([$default_type_atomic]);
}

TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
clone $param->type,
$template_result,
$codebase,
Expand Down
Expand Up @@ -115,6 +115,7 @@ public static function checkArgumentsMatch(
$max_closure_param_count = count($args) > 2 ? 2 : 1;
}

$new = [];
foreach ($closure_arg_type->getAtomicTypes() as $closure_type) {
self::checkClosureType(
$statements_analyzer,
Expand All @@ -127,7 +128,10 @@ public static function checkArgumentsMatch(
$array_arg_types,
$check_functions
);
$new []= $closure_type;
}

$statements_analyzer->node_data->setType($closure_arg->value, $closure_arg_type->getBuilder()->setTypes($new)->freeze());
}
}

Expand Down Expand Up @@ -584,13 +588,12 @@ public static function handleByRefArrayAdjustment(

/**
* @param (TArray|null)[] $array_arg_types
*
*/
private static function checkClosureType(
StatementsAnalyzer $statements_analyzer,
Context $context,
string $method_id,
Atomic $closure_type,
Atomic &$closure_type,
PhpParser\Node\Arg $closure_arg,
int $min_closure_param_count,
int $max_closure_param_count,
Expand Down Expand Up @@ -726,10 +729,10 @@ private static function checkClosureType(
}
}
} else {
$closure_types = [$closure_type];
$closure_types = [&$closure_type];
}

foreach ($closure_types as $closure_type) {
foreach ($closure_types as &$closure_type) {
if ($closure_type->params === null) {
continue;
}
Expand All @@ -755,7 +758,7 @@ private static function checkClosureTypeArgs(
StatementsAnalyzer $statements_analyzer,
Context $context,
string $method_id,
Atomic $closure_type,
Atomic &$closure_type,
PhpParser\Node\Arg $closure_arg,
int $min_closure_param_count,
int $max_closure_param_count,
Expand Down Expand Up @@ -863,7 +866,7 @@ private static function checkClosureTypeArgs(
$context->calling_method_id ?: $context->calling_function_id
);

$closure_type->replaceTemplateTypesWithArgTypes(
$closure_type = $closure_type->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
Expand Down
Expand Up @@ -240,8 +240,7 @@ private static function resolveTemplateParam(
}
} else {
if ($template_result !== null) {
$type_extends_atomic = clone $type_extends_atomic;
$type_extends_atomic->replaceTemplateTypesWithArgTypes(
$type_extends_atomic = $type_extends_atomic->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
Expand Down
Expand Up @@ -849,9 +849,7 @@ private static function handleRegularMixins(
$lhs_var_id === '$this'
);

$lhs_type_part = clone $mixin;

$lhs_type_part->replaceTemplateTypesWithArgTypes(
$lhs_type_part = $mixin->replaceTemplateTypesWithArgTypes(
new TemplateResult([], $mixin_class_template_params ?: []),
$codebase
);
Expand Down
Expand Up @@ -207,7 +207,7 @@ public static function analyze(
if ($method_storage && $method_storage->if_this_is_type) {
$method_template_result = new TemplateResult($method_storage->template_types ?: [], []);

TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
clone $method_storage->if_this_is_type,
$method_template_result,
$codebase,
Expand Down
15 changes: 10 additions & 5 deletions src/Psalm/Internal/Codebase/Methods.php
Expand Up @@ -887,12 +887,17 @@ public function getMethodReturnType(
if ((!$old_contained_by_new && !$new_contained_by_old)
|| ($old_contained_by_new && $new_contained_by_old)
) {
$attempted_intersection = null;
if ($old_contained_by_new) { //implicitly $new_contained_by_old as well
$attempted_intersection = Type::intersectUnionTypes(
$candidate_type,
$overridden_storage->return_type,
$source_analyzer->getCodebase()
);
try {
$attempted_intersection = Type::intersectUnionTypes(
$candidate_type,
$overridden_storage->return_type,
$source_analyzer->getCodebase()
);
} catch (InvalidArgumentException $e) {
// TODO: fix
}
} else {
$attempted_intersection = Type::intersectUnionTypes(
$overridden_storage->return_type,
Expand Down
9 changes: 3 additions & 6 deletions src/Psalm/Internal/Type/AssertionReconciler.php
Expand Up @@ -402,14 +402,12 @@ private static function refine(
&& ($codebase->classExists($existing_var_type_part->value)
|| $codebase->interfaceExists($existing_var_type_part->value))
) {
$existing_var_type_part = clone $existing_var_type_part;
$existing_var_type_part->addIntersectionType($new_type_part);
$existing_var_type_part = $existing_var_type_part->addIntersectionType($new_type_part);
$acceptable_atomic_types[] = $existing_var_type_part;
}

if ($existing_var_type_part instanceof TTemplateParam) {
$existing_var_type_part = clone $existing_var_type_part;
$existing_var_type_part->addIntersectionType($new_type_part);
$existing_var_type_part = $existing_var_type_part->addIntersectionType($new_type_part);
$acceptable_atomic_types[] = $existing_var_type_part;
}
}
Expand Down Expand Up @@ -1617,8 +1615,7 @@ private static function handleIsA(
if ($codebase->classExists($existing_var_type_part->value)
|| $codebase->interfaceExists($existing_var_type_part->value)
) {
$existing_var_type_part = clone $existing_var_type_part;
$existing_var_type_part->addIntersectionType($new_type_part);
$existing_var_type_part = $existing_var_type_part->addIntersectionType($new_type_part);
$acceptable_atomic_types[] = $existing_var_type_part;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php
Expand Up @@ -63,10 +63,10 @@ public static function isContainedBy(

if (($container_type_part instanceof TTemplateParam
|| ($container_type_part instanceof TNamedObject
&& isset($container_type_part->extra_types)))
&& $container_type_part->extra_types))
&& ($input_type_part instanceof TTemplateParam
|| ($input_type_part instanceof TNamedObject
&& isset($input_type_part->extra_types)))
&& $input_type_part->extra_types))
) {
return ObjectComparator::isShallowlyContainedBy(
$codebase,
Expand Down
Expand Up @@ -409,7 +409,7 @@ public static function getCallableFromAtomic(
$input_with_templates = new Atomic\TGenericObject($input_type_part->value, $type_params);
$template_result = new TemplateResult($invokable_storage->template_types ?? [], []);

TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
new Type\Union([$input_with_templates]),
$template_result,
$codebase,
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Type/Comparator/ObjectComparator.php
Expand Up @@ -115,7 +115,7 @@ private static function getIntersectionTypes(Atomic $type_part): array
$type_part = clone $type_part;

$extra_types = $type_part->extra_types;
$type_part->extra_types = null;
$type_part->extra_types = [];

$extra_types[$type_part->getKey()] = $type_part;

Expand Down

0 comments on commit 55e6804

Please sign in to comment.