Skip to content

Commit

Permalink
Add array-key type and improve general type handling
Browse files Browse the repository at this point in the history
Fixes #1144
  • Loading branch information
muglug committed Jan 5, 2019
1 parent 2dc3d96 commit 9d8c279
Show file tree
Hide file tree
Showing 50 changed files with 850 additions and 238 deletions.
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Analyzer/CommentAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CommentAnalyzer
/**
* @param string $comment
* @param Aliases $aliases
* @param array<string, string>|null $template_type_map
* @param array<string, Type\Union>|null $template_type_map
* @param int|null $var_line_number
* @param int|null $came_from_line_number what line number in $source that $comment came from
* @param array<string, array<int, string>> $type_aliases
Expand Down
14 changes: 7 additions & 7 deletions src/Psalm/Internal/Analyzer/FunctionAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public static function getReturnTypeFromCallMapWithArgs(
if (!$call_args) {
switch ($call_map_key) {
case 'getenv':
return new Type\Union([new Type\Atomic\TArray([Type::getMixed(), Type::getString()])]);
return new Type\Union([new Type\Atomic\TArray([Type::getArrayKey(), Type::getString()])]);

case 'gettimeofday':
return new Type\Union([
Expand Down Expand Up @@ -313,7 +313,7 @@ public static function getReturnTypeFromCallMapWithArgs(
if ($value_type) {
return new Type\Union([
new Type\Atomic\TArray([
Type::getMixed(),
Type::getArrayKey(),
$value_type
])
]);
Expand Down Expand Up @@ -365,7 +365,7 @@ public static function getReturnTypeFromCallMapWithArgs(
}
}

$result_key_type = Type::getMixed();
$result_key_type = Type::getArrayKey();
$result_element_type = null;
// calculate results
if ($row_shape instanceof Type\Atomic\ObjectLike) {
Expand Down Expand Up @@ -806,7 +806,7 @@ private static function getArrayMergeReturnType(array $call_args)
&& $unpacked_type_part->from_loop_isset
) {
$unpacked_type_part = new Type\Atomic\TArray([
Type::getMixed(),
Type::getArrayKey(),
Type::getMixed(true)
]);
} else {
Expand Down Expand Up @@ -842,8 +842,8 @@ private static function getArrayMergeReturnType(array $call_args)
if ($inner_value_types) {
return new Type\Union([
new Type\Atomic\TArray([
TypeCombination::combineTypes($inner_key_types, true),
TypeCombination::combineTypes($inner_value_types, true),
TypeCombination::combineTypes($inner_key_types, null, true),
TypeCombination::combineTypes($inner_value_types, null, true),
]),
]);
}
Expand Down Expand Up @@ -1035,7 +1035,7 @@ private static function getArrayMapReturnType(
if ($array_arg_type instanceof Type\Atomic\ObjectLike) {
$generic_key_type = $array_arg_type->getGenericKeyType();
} else {
$generic_key_type = $array_arg_type ? clone $array_arg_type->type_params[0] : Type::getMixed();
$generic_key_type = $array_arg_type ? clone $array_arg_type->type_params[0] : Type::getArrayKey();
}
} else {
$generic_key_type = Type::getInt();
Expand Down
3 changes: 2 additions & 1 deletion src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,8 @@ function (Clause $c) use ($mixed_var_ids) {
) {
$combined_type = Type::combineUnionTypes(
$context->vars_in_scope[$var_id],
$type
$type,
$codebase
);

if ($combined_type->equals($context->vars_in_scope[$var_id])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public static function analyze(

$int_offset_diff = 0;

$codebase = $statements_analyzer->getCodebase();

/** @var int $int_offset */
foreach ($stmt->items as $int_offset => $item) {
if ($item === null) {
Expand All @@ -72,7 +74,7 @@ public static function analyze(
}

if ($item_key_type) {
$item_key_type = Type::combineUnionTypes($key_type, $item_key_type, false, true, 30);
$item_key_type = Type::combineUnionTypes($key_type, $item_key_type, $codebase, false, true, 30);
} else {
$item_key_type = $key_type;
}
Expand Down Expand Up @@ -132,6 +134,7 @@ public static function analyze(
$item_value_type = Type::combineUnionTypes(
$item->value->inferredType,
clone $item_value_type,
$codebase,
false,
true,
30
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public static function updateArrayType(
// fall through
}

$codebase = $statements_analyzer->getCodebase();

$root_type = isset($root_array_expr->inferredType) ? $root_array_expr->inferredType : Type::getMixed();

if ($root_type->hasMixed()) {
Expand Down Expand Up @@ -270,6 +272,7 @@ public static function updateArrayType(
$new_child_type = Type::combineUnionTypes(
$child_stmt->inferredType,
$array_assignment_type,
$codebase,
true,
false
);
Expand All @@ -287,6 +290,7 @@ public static function updateArrayType(
$new_child_type = Type::combineUnionTypes(
$child_stmt->inferredType,
$array_assignment_type,
$codebase,
true,
false
);
Expand Down Expand Up @@ -356,6 +360,7 @@ public static function updateArrayType(
$new_child_type = Type::combineUnionTypes(
$root_type,
$array_assignment_type,
$codebase,
true,
false
);
Expand Down Expand Up @@ -409,6 +414,7 @@ public static function updateArrayType(
$new_child_type = Type::combineUnionTypes(
$root_type,
$array_assignment_type,
$codebase,
true,
false
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -959,13 +959,17 @@ public static function analyzeNonDivOperands(

$result_type_member = new Type\Union([new ObjectLike($properties)]);
} else {
$result_type_member = TypeCombination::combineTypes([$left_type_part, $right_type_part], true);
$result_type_member = TypeCombination::combineTypes(
[$left_type_part, $right_type_part],
$codebase,
true
);
}

if (!$result_type) {
$result_type = $result_type_member;
} else {
$result_type = Type::combineUnionTypes($result_type_member, $result_type, true);
$result_type = Type::combineUnionTypes($result_type_member, $result_type, $codebase, true);
}

if ($left instanceof PhpParser\Node\Expr\ArrayDimFetch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,6 @@ public static function analyze(
$function_storage->assertions,
$stmt->args,
$generic_params ?: [],
$function_storage->template_typeof_params ?: [],
$context,
$statements_analyzer
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,12 @@ public static function analyze(

foreach ($lhs_types as $lhs_type_part) {
if ($lhs_type_part instanceof Type\Atomic\TGenericParam
&& $lhs_type_part->extends !== 'mixed'
&& !$lhs_type_part->as->isMixed()
) {
$extra_types = $lhs_type_part->extra_types;

$lhs_type_part = array_values(
Type::parseString($lhs_type_part->extends)->getTypes()
$lhs_type_part->as->getTypes()
)[0];

$lhs_type_part->from_docblock = true;
Expand Down Expand Up @@ -448,11 +448,11 @@ function (PhpParser\Node\Arg $arg) {

foreach ($intersection_types as $intersection_type) {
if ($intersection_type instanceof Type\Atomic\TGenericParam) {
if ($intersection_type->extends !== 'mixed'
&& $intersection_type->extends !== 'object'
if (!$intersection_type->as->isMixed()
&& !$intersection_type->as->hasObject()
) {
$intersection_type = array_values(
Type::parseString($intersection_type->extends)->getTypes()
$intersection_type->as->getTypes()
)[0];

if (!$intersection_type instanceof TNamedObject) {
Expand Down Expand Up @@ -763,7 +763,6 @@ function (PhpParser\Node\Arg $arg) {
$method_storage->assertions,
$args,
$class_template_params ?: [],
$method_storage->template_typeof_params ?: [],
$context,
$statements_analyzer
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static function analyze(
if (!isset($stmt->inferredType)) {
$new_type_part = new Type\Atomic\TGenericParam(
$lhs_type_part->param_name,
$lhs_type_part->extends
Type::parseString($lhs_type_part->as)
);

if ($new_type) {
Expand All @@ -143,7 +143,7 @@ public static function analyze(
) {
if (!isset($stmt->inferredType)) {
$class_name = $lhs_type_part instanceof Type\Atomic\TClassString
? $lhs_type_part->extends
? $lhs_type_part->as
: $lhs_type_part->value;

if ($lhs_type_part instanceof Type\Atomic\TClassString) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,40 @@ public static function analyze(
if ($lhs_type_part instanceof TNamedObject) {
$fq_class_name = $lhs_type_part->value;
} elseif ($lhs_type_part instanceof Type\Atomic\TClassString
&& $lhs_type_part->extends !== 'object'
&& $lhs_type_part->as !== 'object'
) {
$fq_class_name = $lhs_type_part->extends;
$fq_class_name = $lhs_type_part->as;
} elseif ($lhs_type_part instanceof Type\Atomic\TLiteralClassString) {
$fq_class_name = $lhs_type_part->value;
} elseif ($lhs_type_part instanceof Type\Atomic\TGenericParam
&& $lhs_type_part->extends !== 'mixed'
&& $lhs_type_part->extends !== 'object'
&& !$lhs_type_part->as->isMixed()
&& !$lhs_type_part->as->hasObject()
) {
$fq_class_name = $lhs_type_part->extends;
$fq_class_name = null;

foreach ($lhs_type_part->as->getTypes() as $generic_param_type) {
if (!$generic_param_type instanceof TNamedObject) {
continue 2;
}

$fq_class_name = $generic_param_type->value;
break;
}

if (!$fq_class_name) {
if (IssueBuffer::accepts(
new UndefinedClass(
'Type ' . $lhs_type_part->as . ' cannot be called as a class',
new CodeLocation($statements_analyzer->getSource(), $stmt),
(string) $lhs_type_part
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}

continue;
}
} else {
if ($lhs_type_part instanceof Type\Atomic\TMixed
|| $lhs_type_part instanceof Type\Atomic\TGenericParam
Expand Down Expand Up @@ -556,7 +580,6 @@ function (PhpParser\Node\Arg $arg) {
$method_storage->assertions,
$stmt->args,
$found_generic_params ?: [],
$method_storage->template_typeof_params ?: [],
$context,
$statements_analyzer
);
Expand Down
30 changes: 3 additions & 27 deletions src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ protected static function checkFunctionArguments(
$context->vars_in_scope[$var_id]->removeType('array');
$context->vars_in_scope[$var_id]->addType(
new TArray(
[Type::getMixed(), Type::getMixed()]
[Type::getArrayKey(), Type::getMixed()]
)
);
}
Expand Down Expand Up @@ -952,18 +952,6 @@ protected static function checkFunctionLikeArgumentsMatch(
}

if ($function_storage) {
if (isset($function_storage->template_typeof_params[$argument_offset])) {
$param_type_nullable = $param_type->isNullable();
$param_type = new Type\Union([
new Type\Atomic\TGenericParamClass(
$function_storage->template_typeof_params[$argument_offset]
)
]);
if ($param_type_nullable) {
$param_type->addType(new Type\Atomic\TNull);
}
}

if ($existing_generic_params_to_strings) {
$empty_generic_params = [];

Expand Down Expand Up @@ -1744,7 +1732,7 @@ public static function checkFunctionArgumentType(
if (IssueBuffer::accepts(
new InvalidScalarArgument(
'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' .
$param_type . ', ' . $input_type . ' provided',
$param_type->getId() . ', ' . $input_type->getId() . ' provided',
$code_location
),
$statements_analyzer->getSuppressedIssues()
Expand Down Expand Up @@ -2152,7 +2140,6 @@ protected static function getExistingFunctionId(
* @param array<int, PhpParser\Node\Arg> $args
* @param Context $context
* @param array<string, Type\Union> $generic_params,
* @param array<int, string> $template_typeof_params
* @param StatementsAnalyzer $statements_analyzer
*
* @return void
Expand All @@ -2162,7 +2149,6 @@ protected static function applyAssertionsToContext(
array $assertions,
array $args,
array $generic_params,
array $template_typeof_params,
Context $context,
StatementsAnalyzer $statements_analyzer
) {
Expand Down Expand Up @@ -2206,17 +2192,7 @@ protected static function applyAssertionsToContext(
$rule = substr($rule, 1);
}

if (($offset = array_search($rule, $template_typeof_params, true)) !== false) {
if (isset($args[$offset]->value->inferredType)) {
$templated_type = $args[$offset]->value->inferredType;

if ($templated_type->isSingleStringLiteral()) {
$type_assertions[$assertion_var_id] = [
[$prefix . $templated_type->getSingleStringLiteral()->value]
];
}
}
} elseif (isset($generic_params[$rule])) {
if (isset($generic_params[$rule])) {
if ($generic_params[$rule]->hasMixed()) {
continue;
}
Expand Down

0 comments on commit 9d8c279

Please sign in to comment.