protected function verifyReturn($function, State $state) { if (!$function->stmts) { // interface return []; } $errors = []; if ($function->returnType) { $type = Type::fromDecl($function->returnType->value); } else { $type = Type::extractTypeFromComment("return", $function->getAttribute('doccomment')); if (Type::mixed()->equals($type)) { // only verify actual types return $errors; } } $returns = $this->findReturnBlocks($function->stmts); foreach ($returns as $return) { if (!$return || !$return->expr) { // Default return, no if ($type->allowsNull()) { continue; } if (!$return) { $errors[] = ["Default return found for non-null type {$type}", $function]; } else { $errors[] = ["Explicit null return found for non-null type {$type}", $return]; } } elseif (!$return->expr->type) { var_dump($return->expr); $errors[] = ["Could not resolve type for return", $return]; } else { if (!$state->resolver->resolves($return->expr->type, $type)) { $errors[] = ["Type mismatch on return value, found {$return->expr->type} expecting {$type}", $return]; } } } return $errors; }
/** * @return array */ protected function verifyCall($func, $call, $state, $name) { $errors = []; foreach ($func->params as $idx => $param) { if (!isset($call->args[$idx])) { if (!$param->defaultVar) { $errors[] = ["Missing required argument {$idx} for call {$name}()", $call]; } continue; } if ($param->type) { $type = Type::fromDecl($param->type->value); } else { $type = Type::extractTypeFromComment("param", $param->function->getAttribute('doccomment'), $param->name->value); if (Type::mixed()->equals($type)) { continue; } } if (!$state->resolver->resolves($call->args[$idx]->type, $type)) { $errors[] = ["Type mismatch on {$name}() argument {$idx}, found {$call->args[$idx]->type} expecting {$type}", $call]; } } return $errors; }
private function resolveMethodCall($class, $name, Op $op, SplObjectStorage $resolved) { if (!$name instanceof Operand\Literal) { // Variable Method Call return false; } $name = strtolower($name->value); if ($resolved->contains($class)) { $userType = ''; if ($resolved[$class]->type === Type::TYPE_STRING) { if (!$class instanceof Operand\Literal) { // variable class name, for now just return object return [Type::mixed()]; } $userType = $class->value; } elseif ($resolved[$class]->type !== Type::TYPE_OBJECT) { return false; } else { $userType = $resolved[$class]->userType; } $types = []; $className = strtolower($userType); if (!isset($this->state->classResolves[$className])) { if (isset($this->state->internalTypeInfo->methods[$className])) { $types = []; foreach ($this->state->internalTypeInfo->methods[$className]['extends'] as $child) { if (isset($this->state->internalTypeInfo->methods[$child]['methods'][$name])) { $method = $this->state->internalTypeInfo->methods[$child]['methods'][$name]; if ($method['return']) { $types[] = Type::fromDecl($method['return']); } } } if (!empty($types)) { return $types; } } return false; } foreach ($this->state->classResolves[$className] as $class) { $method = $this->findMethod($class, $name); if (!$method) { continue; } $doc = Type::extractTypeFromComment("return", $method->getAttribute('doccomment')); if (!$method->returnType) { $types[] = $doc; } else { $decl = Type::fromDecl($method->returnType->value); if ($this->state->resolver->resolves($doc, $decl)) { // doc is a subset $types[] = $doc; } else { $types[] = $decl; } } } if (!empty($types)) { return $types; } return false; } return false; }