Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/ISSUE_TEMPLATE/1-bug-report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Bug report
description: If something isn't working as expected.
title: ""
labels: ["bug"]
body:
- type: textarea
id: description
attributes:
label: Bug report
description: Please describe your problem here
placeholder: Leave a comment
validations:
required: true

- type: input
id: reproduction
attributes:
label: Code snippet that reproduces the problem
description: |
Please reproduce the issue with the [on-line TypePHP playground](https://typephp-php.github.io/docs/playground), get a unique URL with the Share button and post it here.
placeholder: https://typephp-php.github.io/docs/playground#code=<unique_id>
validations:
required: false

- type: textarea
id: expected
attributes:
label: Expected output
description: Was the issue reported incorrectly? Or should TypePHP detect an issue with the code but doesn't?
placeholder: Leave a comment
validations:
required: true
32 changes: 32 additions & 0 deletions .github/ISSUE_TEMPLATE/2-feature-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Feature request
description: I have a suggestion (and may want to implement it)!
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Have an idea to improve TypePHP? We would love to hear it!
- type: textarea
id: proposal
attributes:
label: Proposed Feature / Syntax
description: Describe the feature or type annotation you would like TypePHP to support.
placeholder: E.g., Support for a new scalar refinement, shape feature, or CLI option...
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation / Use Case
description: Why should this be added to TypePHP? What problem does it solve?
validations:
required: true
- type: textarea
id: example
attributes:
label: Code Example
description: Show a code snippet demonstrating how this feature would look and behave.
render: php
validations:
required: false
31 changes: 31 additions & 0 deletions .github/ISSUE_TEMPLATE/3-type-mismatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Function signature or stub mismatch
description: Some built-in PHP function or vendor stub expects or returns a different type than reported.
title: "[Type Mismatch]: "
labels: ["types", "stubs"]
body:
- type: markdown
attributes:
value: |
Use this template if TypePHP reports an invalid type on a built-in PHP function, or if a DocBlock contract contradicts real PHP behavior.
- type: input
id: target
attributes:
label: Affected Function, Method, or Class
placeholder: E.g., array_map(), DateTime::format(), or Vendor\Package\Service
validations:
required: true
- type: textarea
id: current-vs-expected
attributes:
label: Current vs Expected Type Contract
description: What does TypePHP currently enforce versus what does PHP actually allow/return?
validations:
required: true
- type: input
id: playground
attributes:
label: Playground Link (Optional)
description: If possible, provide a playground reproduction.
placeholder: https://typephp-php.github.io/docs/playground#code=...
validations:
required: false
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://github.com/typephp-php/typephp/security/policy
about: Please review our security policy for more details.
- name: Support question
url: https://github.com/typephp-php/typephp/discussions/new?category=q-a
about: Please open a new discussion instead. Thank you.
10 changes: 8 additions & 2 deletions src/Internal/Ast/ContractVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ private function enterClassLike(Node\Stmt\Class_|Node\Stmt\Interface_|Node\Stmt\
{
$typeName = $this->resolveQualifiedName($node->name);
$hasInheritance = true;
$hasRealInheritance = false;
$hasPropertyWithDoc = false;
$isReadonly = false;
$hasTemplates = false;
Expand Down Expand Up @@ -350,11 +351,15 @@ private function enterClassLike(Node\Stmt\Class_|Node\Stmt\Interface_|Node\Stmt\
$hasTemplates
|| str_contains($doc->getText(), '@phpstan-')
|| str_contains($doc->getText(), '@psalm-')
|| str_contains($doc->getText(), '@method')
|| str_contains($doc->getText(), '@property')
);

$hasInheritance = $hasExtends || $hasImplements || $hasTraits || $hasClassDoc;
$hasRealInheritance = $hasExtends || $hasImplements || $hasTraits;
$hasInheritance = $hasRealInheritance || $hasClassDoc;
} elseif ($node instanceof Node\Stmt\Enum_) {
$hasInheritance = $node->implements !== [];
$hasRealInheritance = $node->implements !== [];
$hasInheritance = $hasRealInheritance;
} else {
$doc = $node->getDocComment();
$hasTemplates = $doc !== null && (
Expand All @@ -368,6 +373,7 @@ private function enterClassLike(Node\Stmt\Class_|Node\Stmt\Interface_|Node\Stmt\
'name' => $typeName,
'isAnonymous' => ($node instanceof Node\Stmt\Class_ && $node->name === null),
'hasInheritance' => $hasInheritance,
'hasRealInheritance' => $hasRealInheritance,
'hasPropertyWithDoc' => $hasPropertyWithDoc,
'isReadonly' => $isReadonly,
'hasTemplates' => $hasTemplates,
Expand Down
40 changes: 22 additions & 18 deletions src/Internal/Ast/FunctionContractInjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class FunctionContractInjector
];

/**
* @param array{hasInheritance?: bool, hasPropertyWithDoc?: bool, isReadonly?: bool, hasTemplates?: bool}|null $classContext
* @param array{hasInheritance?: bool, hasRealInheritance?: bool, hasPropertyWithDoc?: bool, isReadonly?: bool, hasTemplates?: bool}|null $classContext
*/
public static function inject(Node\Stmt\Function_|Node\Stmt\ClassMethod $node, ?array $classContext = null): void
{
Expand All @@ -41,6 +41,7 @@ public static function inject(Node\Stmt\Function_|Node\Stmt\ClassMethod $node, ?
$docText = $doc !== null ? $doc->getText() : '';

$hasInheritance = $classContext['hasInheritance'] ?? true;
$hasRealInheritance = $classContext['hasRealInheritance'] ?? $hasInheritance;
$hasPropertyWithDoc = $classContext['hasPropertyWithDoc'] ?? true;
$isReadonlyClass = $classContext['isReadonly'] ?? false;
$hasClassTemplates = $classContext['hasTemplates'] ?? false;
Expand Down Expand Up @@ -80,7 +81,7 @@ public static function inject(Node\Stmt\Function_|Node\Stmt\ClassMethod $node, ?
$hasParamOut = $byRefParams !== [] && ($hasParamOutDoc || $hasInheritance);

$hasSelfOutDoc = str_contains($docText, 'self-out') || str_contains($docText, 'this-out');
$hasSelfOut = $isClassMethod && ! $node->isStatic() && ($hasSelfOutDoc || ($hasClassTemplates && $hasInheritance));
$hasSelfOut = $isClassMethod && ! $node->isStatic() && ($hasSelfOutDoc || ($hasClassTemplates && $hasRealInheritance));

$hasReturnDoc = str_contains($docText, '@return')
|| str_contains($docText, '@phpstan-return')
Expand Down Expand Up @@ -275,7 +276,7 @@ private static function resolveThisArg(bool $isClassMethod, Node\Stmt\Function_|

private static function isGenerator(Node\Stmt\Function_|Node\Stmt\ClassMethod $node): bool
{
$visitor = new class () extends NodeVisitorAbstract {
$visitor = new class() extends NodeVisitorAbstract {
public bool $isGen = false;

public function enterNode(Node $n): ?int
Expand Down Expand Up @@ -357,24 +358,30 @@ private static function buildSetupScopeStmt(array $params, Node\Expr $thisArg):

$cacheKeyExpr = new Node\Scalar\MagicConst\Method();

$noParamFetch = new Node\Expr\StaticPropertyFetch(
new Node\Name\FullyQualified('TypePHP\Internal\Checker\ParamChecker'),
'noParamContractCache'
);
$noParamFetch->setAttribute('typephp_checked', true);

$noParamCacheCheck = new Node\Expr\BooleanNot(
new Node\Expr\Isset_([
new Node\Expr\ArrayDimFetch(
new Node\Expr\StaticPropertyFetch(
new Node\Name\FullyQualified('TypePHP\Internal\Checker\ParamChecker'),
'noParamContractCache'
),
$noParamFetch,
$cacheKeyExpr
),
])
);

$hasTemplatesFetch = new Node\Expr\StaticPropertyFetch(
new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker'),
'hasMethodTemplatesCache'
);
$hasTemplatesFetch->setAttribute('typephp_checked', true);

$hasTemplatesCheck = new Node\Expr\BinaryOp\Coalesce(
new Node\Expr\ArrayDimFetch(
new Node\Expr\StaticPropertyFetch(
new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker'),
'hasMethodTemplatesCache'
),
$hasTemplatesFetch,
$cacheKeyExpr
),
new Node\Expr\ConstFetch(new Node\Name('false'))
Expand Down Expand Up @@ -671,10 +678,8 @@ public static function buildWrappedYieldNode(Node\Expr\Yield_ $n, Node\Expr $thi
private static function wrapGeneratorReturns(array $stmts, Node\Expr $thisArg): array
{
$traverser = new NodeTraverser();
$traverser->addVisitor(new class ($thisArg) extends NodeVisitorAbstract {
public function __construct(private Node\Expr $thisArg)
{
}
$traverser->addVisitor(new class($thisArg) extends NodeVisitorAbstract {
public function __construct(private Node\Expr $thisArg) {}

public function enterNode(Node $n): int|Node|null
{
Expand Down Expand Up @@ -733,7 +738,7 @@ private static function wrapNonGeneratorReturns(
bool $hasSelfOut = false
): array {
$traverser = new NodeTraverser();
$traverser->addVisitor(new class ($thisArg, $isNativeVoid, $needsReturnVars, $hasReturn, $byRefParams, $hasSelfOut) extends NodeVisitorAbstract {
$traverser->addVisitor(new class($thisArg, $isNativeVoid, $needsReturnVars, $hasReturn, $byRefParams, $hasSelfOut) extends NodeVisitorAbstract {
/**
* @param array<string> $byRefParams
*/
Expand All @@ -744,8 +749,7 @@ public function __construct(
private bool $hasReturn,
private array $byRefParams,
private bool $hasSelfOut
) {
}
) {}

public function enterNode(Node $n): int|array|null
{
Expand Down
Loading