/** * Analyzes the return-types of the current function and reports errors, if necessary * * @param PC_Engine_Scope $scope the current scope */ public function analyze($scope) { $funcname = $scope->get_name_of(T_FUNC_C); $classname = $scope->get_name_of(T_CLASS_C); $func = $this->env->get_types()->get_method_or_func($classname, $funcname); if ($func && !$func->is_abstract()) { $hasnull = false; $hasother = false; foreach ($this->allrettypes as $t) { if ($t === null) { $hasnull = true; } else { $hasother = true; } } if (count($this->allrettypes) == 0) { $mtype = PC_Obj_MultiType::create_void(); } else { $mtype = new PC_Obj_MultiType(); foreach ($this->allrettypes as $t) { if ($t !== null) { $mtype->merge($t, false); } else { $mtype->merge(PC_Obj_MultiType::create_void(), false); } } } if ($func->is_constructor()) { if ($hasother) { $this->report($func, 'The constructor of "' . $classname . '" has a return-statement with expression', PC_Obj_Error::E_S_CONSTR_RETURN); } } else { $name = ($classname ? '#' . $classname . '#::' : '') . $funcname; // empty return-expression and non-empty? if ($hasnull && $hasother) { $this->report($func, 'The function/method "' . $name . '" has return-' . 'statements without expression and return-statements with expression', PC_Obj_Error::E_S_MIXED_RET_AND_NO_RET); } $void = new PC_Obj_Type(PC_Obj_Type::VOID); $docreturn = $func->has_return_doc() && !$func->get_return_type()->contains($void); if ($docreturn && !$hasother) { $this->report($func, 'The function/method "' . $name . '" has a return-specification in PHPDoc' . ', but does not return a value', PC_Obj_Error::E_S_RET_SPEC_BUT_NO_RET); } else { if (!$docreturn && !$func->is_anonymous() && $hasother) { $this->report($func, 'The function/method "' . $name . '" has no return-specification in PHPDoc' . ', but does return a value', PC_Obj_Error::E_S_RET_BUT_NO_RET_SPEC); } else { if ($this->has_forbidden($this->allrettypes, $func->get_return_type())) { $this->report($func, 'The return-specification (PHPDoc) of function/method "' . $name . '" does not match with ' . 'the returned values (spec="' . $func->get_return_type() . '", returns="' . $mtype . '")', PC_Obj_Error::E_S_RETURNS_DIFFER_FROM_SPEC); } } } } if ($func->get_return_type()->is_unknown()) { $func->set_return_type($mtype); } } $this->allrettypes = array(); }
public function test__get() { // public mixed __get ( string $name ) $code = '<?php class A { public function __get($name); } class B { /** * @return int */ public function __get($name); } ?>'; list(, $classes, , , $errors) = $this->analyze($code); $params = array(new PC_Obj_Parameter('name', PC_Obj_MultiType::create_string())); self::assert_equals(2, count($errors)); $m = $classes['a']->get_method('__get'); self::assertParamsEqual($params, $m->get_params()); self::assert_equals((string) PC_Obj_MultiType::create_void(), (string) $m->get_return_type()); $m = $classes['b']->get_method('__get'); self::assertParamsEqual($params, $m->get_params()); self::assert_equals((string) PC_Obj_MultiType::create_int(), (string) $m->get_return_type()); $error = $errors[0]; self::assert_equals(PC_Obj_Error::E_S_RET_SPEC_BUT_NO_RET, $error->get_type()); self::assert_regex('/The function\\/method "#A#::__get" has a return-specification in PHPDoc, but does not return a value/', $error->get_msg()); $error = $errors[1]; self::assert_equals(PC_Obj_Error::E_S_RET_SPEC_BUT_NO_RET, $error->get_type()); self::assert_regex('/The function\\/method "#B#::__get" has a return-specification in PHPDoc, but does not return a value/', $error->get_msg()); }
public function test_funcs() { $code = '<?php function a() {} /** * @param string $a */ function b($a) {} class myc2 extends myc { public static function mystatic() {} public function doit() { parent::doit(); self::mystatic(); $this->c(array(),2); } /** * @param array $a * @param int $b */ protected function c($a,$b = 0) {} /** * @param int $a * @param string $b * @param boolean $c * @return int */ private function d($a = 0,$b = "a",$c = false) { $a = $b + $c; return $a; } /** * @param MyClass $c * @param int $d */ public function doit(MyClass $c,$d) { $c->test($d); } } abstract class myc { public abstract function doit(); } ?>'; list($functions, $classes, , $calls, $errors) = $this->analyze($code); self::assert_equals(0, count($errors)); $func = $functions['a']; /* @var $func PC_Obj_Method */ self::assert_equals('a', $func->get_name()); self::assert_equals(0, $func->get_param_count()); self::assert_equals(0, $func->get_required_param_count()); self::assert_equals((string) PC_Obj_MultiType::create_void(), (string) $func->get_return_type()); $func = $functions['b']; self::assert_equals('b', $func->get_name()); self::assert_equals(1, $func->get_param_count()); self::assert_equals(1, $func->get_required_param_count()); self::assert_equals((string) PC_Obj_MultiType::create_void(), (string) $func->get_return_type()); self::assert_equals('string', (string) $func->get_param('a')); $class = $classes['myc2']; $func = $class->get_method('c'); self::assert_equals('c', $func->get_name()); self::assert_equals(2, $func->get_param_count()); self::assert_equals(1, $func->get_required_param_count()); self::assert_equals((string) PC_Obj_MultiType::create_void(), (string) $func->get_return_type()); self::assert_equals('array', (string) $func->get_param('a')); self::assert_equals('integer?', (string) $func->get_param('b')); $func = $class->get_method('d'); self::assert_equals('d', $func->get_name()); self::assert_equals(3, $func->get_param_count()); self::assert_equals(0, $func->get_required_param_count()); self::assert_equals((string) PC_Obj_MultiType::create_int(), (string) $func->get_return_type()); self::assert_equals('integer?', (string) $func->get_param('a')); self::assert_equals('string?', (string) $func->get_param('b')); self::assert_equals('bool?', (string) $func->get_param('c')); $func = $class->get_method('doit'); self::assert_equals('doit', $func->get_name()); self::assert_equals(2, $func->get_param_count()); self::assert_equals(2, $func->get_required_param_count()); self::assert_equals((string) PC_Obj_MultiType::create_void(), (string) $func->get_return_type()); self::assert_equals('MyClass', (string) $func->get_param('c')); self::assert_equals('integer', (string) $func->get_param('d')); self::assert_equals('myc->doit()', (string) $calls[0]); self::assert_equals('myc2::mystatic()', (string) $calls[1]); }
/** * Checks if the given method is "magic". If so it adds automatically parameter-types (if not * already set). * * @param string $classname the class-name * @param PC_Obj_Method $method the method * @return bool true if handled */ private function handle_magic($classname, $method) { // the magic-methods: // public void __set ( string $name , mixed $value ) // public mixed __get ( string $name ) // public bool __isset ( string $name ) // public void __unset ( string $name ) // public mixed __call ( string $name , array $arguments ) // public mixed __callStatic ( string $name , array $arguments ) // * array __sleep( void ) // * void__wakeup( void ) // public string __toString( void ) // public void __invoke( ... ) // public static object __set_state( array $props ) // * void __clone( void ) $ismagic = true; $visibility = PC_Obj_Visible::V_PUBLIC; $static = false; $expected = null; switch (strtolower($method->get_name())) { case '__set': $expected = array(new PC_Obj_Parameter('name', PC_Obj_MultiType::create_string()), new PC_Obj_Parameter('value', new PC_Obj_MultiType())); break; case '__isset': case '__unset': case '__get': $expected = array(new PC_Obj_Parameter('name', PC_Obj_MultiType::create_string())); break; case '__call': case '__callstatic': $expected = array(new PC_Obj_Parameter('name', PC_Obj_MultiType::create_string()), new PC_Obj_Parameter('arguments', PC_Obj_MultiType::create_array())); break; case '__tostring': $expected = array(); break; case '__sleep': case '__wakeup': case '__clone': $visibility = null; // may be private or protected $expected = array(); break; case '__invoke': $static = true; break; case '__set_state': $expected = array(new PC_Obj_Parameter('props', PC_Obj_MultiType::create_array())); break; default: $ismagic = false; break; } if (!$ismagic) { return false; } if ($visibility !== null && $method->get_visibility() != $visibility) { $this->report($method, 'The magic method "#' . $classname . '#::' . $method->get_name() . '" should be public', PC_Obj_Error::E_T_MAGIC_NOT_PUBLIC); } if ($static && !$method->is_static()) { $this->report($method, 'The magic method "#' . $classname . '#::' . $method->get_name() . '" should be static', PC_Obj_Error::E_T_MAGIC_NOT_STATIC); } else { if (!$static && $method->is_static()) { $this->report($method, 'The magic method "#' . $classname . '#::' . $method->get_name() . '" should not be static', PC_Obj_Error::E_T_MAGIC_IS_STATIC); } } if ($expected !== null) { $changed = false; if ($this->check_params($classname, $method, $expected, $method->get_params())) { // set parameter-descriptions $i = 0; foreach ($method->get_params() as $param) { $param->set_mtype($expected[$i++]->get_mtype()); $param->set_has_doc(true); $changed = true; } } $return = new PC_Obj_MultiType(); switch (strtolower($method->get_name())) { case '__set': case '__unset': case '__wakeup': case '__clone': $return = PC_Obj_MultiType::create_void(); break; case '__set_state': $return = PC_Obj_MultiType::create_object(); break; case '__isset': $return = PC_Obj_MultiType::create_bool(); break; case '__sleep': $return = PC_Obj_MultiType::create_array(); break; case '__tostring': $return = PC_Obj_MultiType::create_string(); break; } if ($method->has_return_doc() && !$this->env->get_types()->is_type_conforming($method->get_return_type(), $return)) { // its ok to specify a more specific return-value if the expected one is "mixed" if (!$return->is_unknown()) { $this->report($method, 'The return-type of the magic-method "#' . $classname . '#::' . $method->get_name() . '" is invalid ' . '(expected="' . $return . '", found="' . $method->get_return_type() . '")', PC_Obj_Error::E_T_MAGIC_METHOD_RET_INVALID); } } if ($return !== null && !$method->has_return_doc()) { $method->set_return_type($return); $method->set_has_return_doc(true); $changed = true; } if ($changed) { $this->env->get_storage()->update_function($method, $method->get_class()); } } return true; }