private function ensurePropertyDefined($left, $resultType) { if ($resultType->isUnknownType()) { return; } $property = $this->typeRegistry->getFetchedPropertyByNode($left); if (null !== $property) { assert($property instanceof ClassProperty); // Specifically ignore anything that is being set in a tearDown() method. // TODO: We should think about introducing a general concept for this which takes // inter procedural control flow into account. $scopeRoot = $this->syntacticScope->getRootNode(); if (!$scopeRoot instanceof BlockNode && !$scopeRoot instanceof \PHPParser_Node_Expr_Closure) { $testCase = $this->typeRegistry->getClassOrCreate('PHPUnit_Framework_TestCase'); $method = $this->typeRegistry->getFunctionByNode($scopeRoot); if ($property->getDeclaringClassType()->isSubtypeOf($testCase) && null !== ($thisType = $this->syntacticScope->getTypeOfThis()) && $thisType->isSubtypeOf($testCase) && null !== $method && strtolower($method->getName()) === 'teardown') { return; } } if (null !== ($type = $property->getPhpType())) { $newType = $this->typeRegistry->createUnionType(array($type, $resultType)); } else { $newType = $resultType; } $property->setPhpType($newType); if ($astNode = $property->getAstNode()) { $astNode->setAttribute('type', $newType); } } }
private function getThisType(\PHPParser_Node $node, Scope $parentScope = null) { $parent = $node; while (null !== $parent) { // As of PHP 5.4, closures inherit the this type of their parent scope. Since there is currently no way to // configure the PHP version that we build against, we will simply always infer the ThisType. Other passes, // can then perform checks whether ``$this`` may be accessed, allowing us to produce better error messages. if ($parent instanceof \PHPParser_Node_Expr_Closure) { if (null === $parentScope) { return null; } return $parentScope->getTypeOfThis(); } if (NodeUtil::isMethodContainer($parent)) { $name = implode("\\", $parent->namespacedName->parts); return $this->typeRegistry->getClassOrCreate($name); } $parent = $parent->getAttribute('parent'); } return null; }
private function attachLiteralTypes(\PHPParser_Node $node, \PHPParser_Node $parent = null) { switch (true) { case $node instanceof \PHPParser_Node_Name_FullyQualified: $node->setAttribute('type', $this->typeRegistry->getClassOrCreate(implode("\\", $node->parts))); break; case $node instanceof \PHPParser_Node_Name: if ($parent instanceof \PHPParser_Node_Expr_New || $parent instanceof \PHPParser_Node_Expr_StaticPropertyFetch || $parent instanceof \PHPParser_Node_Expr_StaticCall || $parent instanceof \PHPParser_Node_Expr_Instanceof || $parent instanceof \PHPParser_Node_Stmt_Catch) { $name = implode("\\", $node->parts); $lowerName = strtolower($name); if ('static' === $name) { $node->setAttribute('type', $this->typeRegistry->getThisType($this->scope->getTypeOfThis())); } else { if ('self' === $name) { $node->setAttribute('type', $this->scope->getTypeOfThis()); } else { if ('parent' === $name) { $thisType = $this->scope->getTypeOfThis()->toMaybeObjectType(); if (null === $thisType || !$thisType->isClass() || null === $thisType->getSuperClassType()) { $node->setAttribute('type', $this->typeRegistry->getNativeType('unknown')); } else { $node->setAttribute('type', $thisType->getSuperClassType()); } } else { $node->setAttribute('type', $this->typeRegistry->getClassOrCreate($name)); } } } } break; case $node instanceof \PHPParser_Node_Expr_Array: case $node instanceof \PHPParser_Node_Expr_Cast_Array: // We only do attach the generic array type on the first build. // For subsequent builds, other passes likely have already made // the array type more specific. if (null === $node->getAttribute('type')) { $node->setAttribute('type', $this->typeRegistry->getNativeType('array')); } break; case $node instanceof \PHPParser_Node_Expr_UnaryMinus: case $node instanceof \PHPParser_Node_Expr_UnaryPlus: case $node instanceof \PHPParser_Node_Scalar_LNumber: case $node instanceof \PHPParser_Node_Scalar_LineConst: $node->setAttribute('type', $this->typeRegistry->getNativeType('integer')); break; case $node instanceof \PHPParser_Node_Scalar_DNumber: $node->setAttribute('type', $this->typeRegistry->getNativeType('double')); break; case $node instanceof \PHPParser_Node_Scalar_String: case $node instanceof \PHPParser_Node_Scalar_FileConst: case $node instanceof \PHPParser_Node_Scalar_DirConst: $node->setAttribute('type', $this->typeRegistry->getNativeType('string')); break; case $node instanceof \PHPParser_Node_Expr_ClassConstFetch: if ($node->class instanceof \PHPParser_Node_Name && in_array($node->class->parts[0], array('self', 'static')) && null !== ($thisType = $this->scope->getTypeOfThis()) && null !== ($objType = $thisType->toMaybeObjectType()) && ($objType->isClass() || $objType->isInterface()) && $objType->hasConstant($node->name)) { $node->setAttribute('type', $objType->getConstant($node->name)->getPhpType()); } break; case $node instanceof \PHPParser_Node_Expr_ConstFetch: $nameParts = $node->name->parts; if (1 === count($nameParts)) { $name = strtolower($nameParts[0]); if ('true' === $name) { $node->setAttribute('type', $this->typeRegistry->getNativeType('boolean')); } else { if ('false' === $name) { $node->setAttribute('type', $this->typeRegistry->getNativeType('false')); } else { if ('null' === $name) { $node->setAttribute('type', $this->typeRegistry->getNativeType('null')); } } } } break; } }