Skip to content

Commit

Permalink
Add support of reason phrase
Browse files Browse the repository at this point in the history
  • Loading branch information
SerafimArts committed Mar 25, 2022
1 parent 004edc8 commit 47c1d05
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 16 deletions.
2 changes: 1 addition & 1 deletion demo/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Account
/**
* @var positive-int|0
*/
#[Invariant('$this->balance >= 0')]
#[Invariant('$this->balance >= 0', 'Balance can not be less than 0')]
protected int $balance = 0;

/**
Expand Down
32 changes: 21 additions & 11 deletions src/Compiler/ContractsParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,35 +82,45 @@ public function invariant(string $file, Attribute $node): iterable
private function statements(string $file, Attribute $node, string $attr, string $stmt): iterable
{
if (\count($node->args) < 1) {
throw SpecificationException::badType($attr, $file, $node->getLine());
throw SpecificationException::invalidExpressionType($attr, $file, $node->getLine());
}

yield $this->extractContractExpression($file, $node->args[0], $attr, $stmt);
yield $this->extractContractExpression($file, $node->args[0], $node->args[1] ?? null, $attr, $stmt);
}

/**
* @psalm-taint-sink file $file
* @param non-empty-string $file
* @param Node\Arg $argument
* @param Node\Arg $expressionArgument
* @param Node\Arg|null $reasonArgument
* @param class-string $attr
* @param class-string $stmt
* @return Statement
*/
private function extractContractExpression(string $file, Node\Arg $argument, string $attr, string $stmt): Statement
{
$value = $argument->value;
private function extractContractExpression(
string $file,
Node\Arg $expressionArgument,
?Node\Arg $reasonArgument,
string $attr,
string $stmt
): Statement {
$exprValue = $expressionArgument->value;
if (!$exprValue instanceof Node\Scalar\String_) {
throw SpecificationException::invalidExpressionType($attr, $file, $exprValue->getStartLine());
}

if (!$value instanceof Node\Scalar\String_) {
throw SpecificationException::badType($attr, $file, $value->getStartLine());
$reasonValue = $reasonArgument?->value;
if ($reasonValue !== null && !$reasonValue instanceof Node\Scalar\String_) {
throw SpecificationException::invalidReasonType($attr, $file, $exprValue->getStartLine());
}

$expression = $this->parse($file, $value->value, $attr, $argument);
$expression = $this->parse($file, $exprValue->value, $attr, $expressionArgument);

$traverser = new NodeTraverser();
$traverser->addVisitor(new ConstReplaceVisitor($file, $value->getStartLine()));
$traverser->addVisitor(new ConstReplaceVisitor($file, $exprValue->getStartLine()));
$traverser->traverse([$expression]);

return new $stmt($expression, $value->value, $file, $argument->getLine());
return new $stmt($expression, $exprValue->value, $reasonValue?->value, $file, $expressionArgument->getLine());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ abstract class Statement implements \Stringable
*
* @param Expr $ast
* @param non-empty-string $expression
* @param string|null $reason
* @param non-empty-string $file
* @param positive-int $line
*/
public function __construct(
Expr $ast,
protected readonly string $expression,
protected readonly ?string $reason,
protected readonly string $file,
protected readonly int $line
) {
Expand Down Expand Up @@ -71,7 +73,7 @@ protected function ternary(Expr $expr, FullyQualified $exception): Expression
return new Expression(
new Expr\Ternary($expr, null, new Expr\Throw_(
new Expr\New_($exception, [
new Arg(new String_($this->expression)),
new Arg(new String_($this->reason ?: $this->expression)),
new Arg(new String_($this->file)),
new Arg(new LNumber($this->line)),
])
Expand Down
25 changes: 22 additions & 3 deletions src/Exception/SpecificationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ class SpecificationException extends \InvalidArgumentException implements
/**
* @var non-empty-string
*/
private const ERROR_INCOMPATIBLE_TYPE = '%s MUST contain PHP code string';
private const ERROR_EXPRESSION_TYPE = 'Argument #1 of %s MUST contain PHP code string';

/**
* @var non-empty-string
*/
private const ERROR_REASON_TYPE = 'Argument #2 of %s MUST contain reason phrase string';

/**
* @param string $message
Expand Down Expand Up @@ -60,9 +65,23 @@ public static function create(string $message, string $file, int $line): static
* @param positive-int $line
* @return static
*/
public static function badType(string $type, string $file, int $line): static
public static function invalidExpressionType(string $type, string $file, int $line): static
{
$message = \sprintf(self::ERROR_EXPRESSION_TYPE, $type);

return self::create($message, $file, $line);
}

/**
* @psalm-taint-sink file $file
* @param non-empty-string $type
* @param non-empty-string $file
* @param positive-int $line
* @return static
*/
public static function invalidReasonType(string $type, string $file, int $line): static
{
$message = \sprintf(self::ERROR_INCOMPATIBLE_TYPE, $type);
$message = \sprintf(self::ERROR_REASON_TYPE, $type);

return self::create($message, $file, $line);
}
Expand Down

0 comments on commit 47c1d05

Please sign in to comment.