From 5673b11cad56b139713cee0fe6189cb88e99b373 Mon Sep 17 00:00:00 2001 From: Marcello Duarte Date: Mon, 3 Aug 2026 16:58:10 +0100 Subject: [PATCH] Take type names from phunkie instead of keeping a second table The REPL named types itself, and had drifted from the library it prints values from. Both showed up in one session: phunkie > true $var0: Bool = true phunkie > ImmList(true) $var1: List = List(true) getType() now delegates scalar naming to phunkie's normaliseType(), so there is one place deciding what a type is called. The hardcoded 'Int', 'Float', 'String', 'Bool' and 'Null' literals scattered through evaluateNode go through getType() for the same reason: each was a second opinion waiting to disagree. get_debug_type() feeds it rather than gettype(), because gettype() returns "NULL" uppercase and spells floats "double", neither of which normaliseType is built around. get_debug_type() returns the same lowercase names reflection uses, which is what that table was written for. Phunkie's names win, so the REPL says Boolean rather than Bool. 57 assertions across 19 feature files updated to match. Resources now report Resource rather than Unknown. The old default arm was only reachable for resources, and Resource is what phunkie calls them, so this closes a drift rather than opening one. Requires phunkie 1.4, which standardises floats on Float. Together the two make the REPL agree with itself: Float and List, Boolean and List. 341, 384, 433 and 484 scenarios on 8.2 through 8.5, phpstan and cs-fixer clean. --- composer.json | 2 +- composer.lock | 14 ++--- features/repl/basic_php_expressions.feature | 10 +-- features/repl/casts.feature | 4 +- features/repl/clone_expressions.feature | 2 +- features/repl/error_suppression.feature | 2 +- features/repl/first_class_callable.feature | 2 +- features/repl/import_command.feature | 4 +- features/repl/instanceof_checks.feature | 22 +++---- features/repl/php8.2/false_type.feature | 8 +-- features/repl/php8.2/true_type.feature | 8 +-- .../repl/php8.3/builtin_functions.feature | 12 ++-- .../repl/php8.3/readonly_amendments.feature | 2 +- .../repl/php8.3/typed_class_constants.feature | 2 +- features/repl/php8.4/array_functions.feature | 16 ++--- .../repl/php8.4/asymmetric_visibility.feature | 2 +- features/repl/php8.4/lazy_objects.feature | 6 +- .../error_handler_introspection.feature | 4 +- features/repl/php8.5/pipe_operator.feature | 2 +- features/repl/phunkie_structures.feature | 2 +- features/repl/scalar_evaluation.feature | 4 +- src/Functions/evaluation.php | 29 ++++----- tests/Unit/Functions/EvaluationTest.php | 61 ++++++++++++++++++- 23 files changed, 140 insertions(+), 80 deletions(-) diff --git a/composer.json b/composer.json index bcf1c67..7e96954 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": "^8.2 || ^8.3 || ^8.4 || ^8.5", - "phunkie/phunkie": "^1.3", + "phunkie/phunkie": "^1.4", "phunkie/effect": "^1.1", "nikic/php-parser": "^5.6" }, diff --git a/composer.lock b/composer.lock index 9dbf55f..586e311 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0a5eec3b6893b5edf3ed95c5a1abd02e", + "content-hash": "75caf47d62bbeccb5eca18b2757ab361", "packages": [ { "name": "nikic/php-parser", @@ -148,16 +148,16 @@ }, { "name": "phunkie/phunkie", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phunkie/phunkie.git", - "reference": "43a800e55e8c91a0f104cdff261039453db605f3" + "reference": "9b7795477c794e6c23fabcab4a1dcf0dae6ccac2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phunkie/phunkie/zipball/43a800e55e8c91a0f104cdff261039453db605f3", - "reference": "43a800e55e8c91a0f104cdff261039453db605f3", + "url": "https://api.github.com/repos/phunkie/phunkie/zipball/9b7795477c794e6c23fabcab4a1dcf0dae6ccac2", + "reference": "9b7795477c794e6c23fabcab4a1dcf0dae6ccac2", "shasum": "" }, "require": { @@ -195,9 +195,9 @@ "description": "Functional structures library for PHP", "support": { "issues": "https://github.com/phunkie/phunkie/issues", - "source": "https://github.com/phunkie/phunkie/tree/1.3.0" + "source": "https://github.com/phunkie/phunkie/tree/1.4.0" }, - "time": "2026-08-02T15:07:02+00:00" + "time": "2026-08-03T15:42:52+00:00" } ], "packages-dev": [ diff --git a/features/repl/basic_php_expressions.feature b/features/repl/basic_php_expressions.feature index 8e979d4..0e754bc 100644 --- a/features/repl/basic_php_expressions.feature +++ b/features/repl/basic_php_expressions.feature @@ -16,12 +16,12 @@ Feature: Evaluating Basic PHP Expressions Scenario: Boolean AND operation Given I start the REPL When I enter "true && false" - Then I should see output containing "$var0: Bool = false" + Then I should see output containing "$var0: Boolean = false" Scenario: Boolean NOT operation Given I start the REPL When I enter "!true" - Then I should see output containing "$var0: Bool = false" + Then I should see output containing "$var0: Boolean = false" Scenario: Simple variable assignment Given I start the REPL @@ -68,17 +68,17 @@ Feature: Evaluating Basic PHP Expressions Scenario: Boolean OR operation Given I start the REPL When I enter "true || false" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: Comparison greater than Given I start the REPL When I enter "5 > 3" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: Comparison equal Given I start the REPL When I enter "5 == 5" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: Unary minus Given I start the REPL diff --git a/features/repl/casts.feature b/features/repl/casts.feature index 04325c5..d6c3e46 100644 --- a/features/repl/casts.feature +++ b/features/repl/casts.feature @@ -26,12 +26,12 @@ Feature: Type casts Scenario: Casting zero to bool Given I start the REPL When I enter "(bool) 0" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: Casting a non-empty string to bool Given I start the REPL When I enter "(bool) \"phunkie\"" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Casting a scalar to array Given I start the REPL diff --git a/features/repl/clone_expressions.feature b/features/repl/clone_expressions.feature index 17c121f..e9c9f2c 100644 --- a/features/repl/clone_expressions.feature +++ b/features/repl/clone_expressions.feature @@ -22,7 +22,7 @@ Feature: Clone expressions Then I should see "$var0: Int = 42" When I type "$original === $copy" And I press enter - Then I should see "$var1: Bool = false" + Then I should see "$var1: Boolean = false" Scenario: Clone with __clone magic method Given I am running the repl diff --git a/features/repl/error_suppression.feature b/features/repl/error_suppression.feature index 0ba37d0..624cb87 100644 --- a/features/repl/error_suppression.feature +++ b/features/repl/error_suppression.feature @@ -12,7 +12,7 @@ Feature: Error Suppression Operator (@) Scenario: Suppress warning from file operation Given I start the REPL When I enter "@file_get_contents('/nonexistent/file.txt')" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: Error suppression in variable assignment Given I start the REPL diff --git a/features/repl/first_class_callable.feature b/features/repl/first_class_callable.feature index 013910e..1ddbd07 100644 --- a/features/repl/first_class_callable.feature +++ b/features/repl/first_class_callable.feature @@ -82,4 +82,4 @@ Feature: First-Class Callable Syntax When I enter "$f1 = strlen(...)" And I enter "$f2 = strlen(...)" And I enter "$f1 === $f2" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" diff --git a/features/repl/import_command.feature b/features/repl/import_command.feature index 91806f5..54ed5f7 100644 --- a/features/repl/import_command.feature +++ b/features/repl/import_command.feature @@ -69,14 +69,14 @@ Feature: Import Command When I enter ":import option/isDefined" Then I should see output containing "imported function \Phunkie\Functions\option\isDefined()" When I enter "isDefined(Some(42))" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Import from option module - isNone Given I start the REPL When I enter ":import option/isNone" Then I should see output containing "imported function \Phunkie\Functions\option\isNone()" When I enter "isNone(None())" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Error when importing non-existent module Given I start the REPL diff --git a/features/repl/instanceof_checks.feature b/features/repl/instanceof_checks.feature index 040b7cd..e6d97c3 100644 --- a/features/repl/instanceof_checks.feature +++ b/features/repl/instanceof_checks.feature @@ -8,7 +8,7 @@ Feature: Instanceof Operator Support When I enter "class Dog {}" And I enter "$dog = new Dog()" And I enter "$dog instanceof Dog" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: instanceof with false result for different class Given I start the REPL @@ -16,7 +16,7 @@ Feature: Instanceof Operator Support And I enter "class Cat {}" And I enter "$dog = new Dog()" And I enter "$dog instanceof Cat" - Then I should see output containing "$var0: Bool = false" + Then I should see output containing "$var0: Boolean = false" Scenario: instanceof with interface Given I start the REPL @@ -24,7 +24,7 @@ Feature: Instanceof Operator Support And I enter "class Bird implements Flyable {}" And I enter "$bird = new Bird()" And I enter "$bird instanceof Flyable" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: instanceof with parent class Given I start the REPL @@ -32,7 +32,7 @@ Feature: Instanceof Operator Support And I enter "class Dog extends Animal {}" And I enter "$dog = new Dog()" And I enter "$dog instanceof Animal" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: instanceof with variable class name Given I start the REPL @@ -40,26 +40,26 @@ Feature: Instanceof Operator Support And I enter "$dog = new Dog()" And I enter "$className = 'Dog'" And I enter "$dog instanceof $className" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: instanceof with null value returns false Given I start the REPL When I enter "class Dog {}" And I enter "$dog = null" And I enter "$dog instanceof Dog" - Then I should see output containing "$var0: Bool = false" + Then I should see output containing "$var0: Boolean = false" Scenario: instanceof with standard PHP class Given I start the REPL When I enter "$date = new DateTime()" And I enter "$date instanceof DateTime" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: instanceof with Phunkie Some monad Given I start the REPL When I enter "$x = Some(42)" And I enter "$x instanceof Phunkie\Types\Some" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: instanceof checks are chainable in expressions Given I start the REPL @@ -67,18 +67,18 @@ Feature: Instanceof Operator Support And I enter "class Cat {}" And I enter "$dog = new Dog()" And I enter "$dog instanceof Dog && !($dog instanceof Cat)" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" Scenario: instanceof with scalar value returns false Given I start the REPL When I enter "class Dog {}" And I enter "$value = 42" And I enter "$value instanceof Dog" - Then I should see output containing "$var0: Bool = false" + Then I should see output containing "$var0: Boolean = false" Scenario: instanceof with anonymous class Given I start the REPL When I enter "interface Runnable { public function run(); }" And I enter "$obj = new class implements Runnable { public function run() { return 'running'; } }" And I enter "$obj instanceof Runnable" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" diff --git a/features/repl/php8.2/false_type.feature b/features/repl/php8.2/false_type.feature index 3a023b2..457b12c 100644 --- a/features/repl/php8.2/false_type.feature +++ b/features/repl/php8.2/false_type.feature @@ -22,7 +22,7 @@ Feature: False Type (PHP 8.2) } """ And I enter "alwaysFalse()" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" # Note: Literal true/false/null types in parameters have a known limitation in REPL # due to how arguments are evaluated before function calls. @@ -41,13 +41,13 @@ Feature: False Type (PHP 8.2) """ And I enter "$checker = new Checker()" And I enter "$checker->check()" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: Arrow function with false return type Given I start the REPL When I enter "$failure = fn(): false => false" And I enter "$failure()" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: False type in union with other types Given I start the REPL @@ -58,4 +58,4 @@ Feature: False Type (PHP 8.2) } """ And I enter "tryOperation()" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" diff --git a/features/repl/php8.2/true_type.feature b/features/repl/php8.2/true_type.feature index 6100fbf..d944b9a 100644 --- a/features/repl/php8.2/true_type.feature +++ b/features/repl/php8.2/true_type.feature @@ -22,7 +22,7 @@ Feature: True Type (PHP 8.2) } """ And I enter "alwaysTrue()" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" # Note: Literal true/false/null types in parameters have a known limitation in REPL # due to how arguments are evaluated before function calls. @@ -41,13 +41,13 @@ Feature: True Type (PHP 8.2) """ And I enter "$validator = new Validator()" And I enter "$validator->validate()" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Arrow function with true return type Given I start the REPL When I enter "$success = fn(): true => true" And I enter "$success()" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: True type in union with other types Given I start the REPL @@ -58,4 +58,4 @@ Feature: True Type (PHP 8.2) } """ And I enter "checkStatus()" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" diff --git a/features/repl/php8.3/builtin_functions.feature b/features/repl/php8.3/builtin_functions.feature index 6f92fa2..3598fcd 100644 --- a/features/repl/php8.3/builtin_functions.feature +++ b/features/repl/php8.3/builtin_functions.feature @@ -6,22 +6,22 @@ Feature: PHP 8.3 Built-in Functions Scenario: json_validate() with valid JSON Given I start the REPL When I enter "json_validate('{\"name\": \"test\", \"value\": 123}')" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: json_validate() with invalid JSON Given I start the REPL When I enter "json_validate('{invalid json}')" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: json_validate() with empty string Given I start the REPL When I enter "json_validate('')" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: json_validate() with valid array JSON Given I start the REPL When I enter "json_validate('[1, 2, 3, 4]')" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: mb_str_pad() basic padding Given I start the REPL @@ -49,11 +49,11 @@ Feature: PHP 8.3 Built-in Functions When I enter "$r = new \Random\Randomizer()" And I enter "$f = $r->getFloat(0, 1)" And I enter "$f >= 0 && $f < 1" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Randomizer::nextFloat() Given I start the REPL When I enter "$r = new \Random\Randomizer()" And I enter "$f = $r->nextFloat()" And I enter "$f >= 0 && $f < 1" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" diff --git a/features/repl/php8.3/readonly_amendments.feature b/features/repl/php8.3/readonly_amendments.feature index b1a87ae..e1c72f0 100644 --- a/features/repl/php8.3/readonly_amendments.feature +++ b/features/repl/php8.3/readonly_amendments.feature @@ -107,7 +107,7 @@ Feature: Readonly Amendments (PHP 8.3) When I enter "$c2->timeout" Then I should see output containing "Int = 60" When I enter "$c2->debug" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: Clone with conditional readonly modification Given I start the REPL diff --git a/features/repl/php8.3/typed_class_constants.feature b/features/repl/php8.3/typed_class_constants.feature index d000c6c..f525d84 100644 --- a/features/repl/php8.3/typed_class_constants.feature +++ b/features/repl/php8.3/typed_class_constants.feature @@ -100,7 +100,7 @@ Feature: Typed Class Constants (PHP 8.3) When I enter "Database::PORT" Then I should see output containing "Int = 3306" When I enter "Database::SSL" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Private typed constant Given I start the REPL diff --git a/features/repl/php8.4/array_functions.feature b/features/repl/php8.4/array_functions.feature index 820caed..acc9d7b 100644 --- a/features/repl/php8.4/array_functions.feature +++ b/features/repl/php8.4/array_functions.feature @@ -51,35 +51,35 @@ Feature: New Array Functions (PHP 8.4) Given I start the REPL When I enter "$numbers = [1, 2, 3, 4]" And I enter "array_any($numbers, fn($n) => $n > 3)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: array_any() with no matches Given I start the REPL When I enter "$numbers = [1, 2, 3]" And I enter "array_any($numbers, fn($n) => $n > 10)" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: array_any() with empty array Given I start the REPL When I enter "array_any([], fn($n) => true)" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: array_all() with all matching Given I start the REPL When I enter "$numbers = [2, 4, 6, 8]" And I enter "array_all($numbers, fn($n) => $n % 2 === 0)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: array_all() with partial match Given I start the REPL When I enter "$numbers = [2, 3, 4]" And I enter "array_all($numbers, fn($n) => $n % 2 === 0)" - Then I should see output containing "Bool = false" + Then I should see output containing "Boolean = false" Scenario: array_all() with empty array Given I start the REPL When I enter "array_all([], fn($n) => false)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: array_all() with associative array Given I start the REPL @@ -92,13 +92,13 @@ Feature: New Array Functions (PHP 8.4) ]; """ And I enter "array_all($products, fn($p) => $p['stock'] > 5)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Chaining array functions Given I start the REPL When I enter "$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" And I enter "array_any($numbers, fn($n) => $n > 5 && $n % 2 === 0)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: array_find() with object array Given I start the REPL diff --git a/features/repl/php8.4/asymmetric_visibility.feature b/features/repl/php8.4/asymmetric_visibility.feature index 6caa793..79770f7 100644 --- a/features/repl/php8.4/asymmetric_visibility.feature +++ b/features/repl/php8.4/asymmetric_visibility.feature @@ -199,4 +199,4 @@ Feature: Asymmetric Visibility (PHP 8.4) """ And I enter "$doc = new Document()" And I enter "$doc->createdAt > 0" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" diff --git a/features/repl/php8.4/lazy_objects.feature b/features/repl/php8.4/lazy_objects.feature index d231629..d509409 100644 --- a/features/repl/php8.4/lazy_objects.feature +++ b/features/repl/php8.4/lazy_objects.feature @@ -99,7 +99,7 @@ Feature: Lazy Objects (PHP 8.4) }); """ And I enter "(new ReflectionClass($lazy))->isUninitializedLazyObject($lazy)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Lazy object becomes initialized after access Given I start the REPL @@ -120,7 +120,7 @@ Feature: Lazy Objects (PHP 8.4) And I enter "$lazy->name" And I enter "$after = (new ReflectionClass($lazy))->isUninitializedLazyObject($lazy)" And I enter "$before && !$after" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Initialize lazy object explicitly Given I start the REPL @@ -171,7 +171,7 @@ Feature: Lazy Objects (PHP 8.4) And I enter "$lazy->increment()" And I enter "(new ReflectionClass($lazy))->resetAsLazyGhost($lazy, function ($object) { $object->__construct(); })" And I enter "(new ReflectionClass($lazy))->isUninitializedLazyObject($lazy)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Lazy object with dependencies Given I start the REPL diff --git a/features/repl/php8.5/error_handler_introspection.feature b/features/repl/php8.5/error_handler_introspection.feature index 214c0c4..8a1aeba 100644 --- a/features/repl/php8.5/error_handler_introspection.feature +++ b/features/repl/php8.5/error_handler_introspection.feature @@ -12,7 +12,7 @@ Feature: Error Handler Introspection (PHP 8.5) Given I start the REPL When I enter "set_exception_handler(function ($e) { return null; })" And I enter "is_callable(get_exception_handler())" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: get_exception_handler() is null once the handler is cleared Given I start the REPL @@ -24,4 +24,4 @@ Feature: Error Handler Introspection (PHP 8.5) Scenario: PHP_BUILD_DATE is available Given I start the REPL When I enter "is_string(PHP_BUILD_DATE)" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" diff --git a/features/repl/php8.5/pipe_operator.feature b/features/repl/php8.5/pipe_operator.feature index 4b95e0f..65184b5 100644 --- a/features/repl/php8.5/pipe_operator.feature +++ b/features/repl/php8.5/pipe_operator.feature @@ -43,7 +43,7 @@ Feature: Pipe Operator (PHP 8.5) Scenario: Pipe binds tighter than comparison Given I start the REPL When I enter "\"beep\" |> strlen(...) == 4" - Then I should see output containing "Bool = true" + Then I should see output containing "Boolean = true" Scenario: Piping into a value that is not callable Given I start the REPL diff --git a/features/repl/phunkie_structures.feature b/features/repl/phunkie_structures.feature index 5c38248..1f0fc93 100644 --- a/features/repl/phunkie_structures.feature +++ b/features/repl/phunkie_structures.feature @@ -42,4 +42,4 @@ Feature: Evaluating Phunkie Data Structures Given I start the REPL When I enter "None" And I enter "$var0->isEmpty()" - Then I should see output containing "$var1: Bool = true" + Then I should see output containing "$var1: Boolean = true" diff --git a/features/repl/scalar_evaluation.feature b/features/repl/scalar_evaluation.feature index c859d46..4af2b3c 100644 --- a/features/repl/scalar_evaluation.feature +++ b/features/repl/scalar_evaluation.feature @@ -16,9 +16,9 @@ Feature: Evaluating Scalar Values Scenario: Evaluating booleans Given I start the REPL When I enter "true" - Then I should see output containing "$var0: Bool = true" + Then I should see output containing "$var0: Boolean = true" When I enter "false" - Then I should see output containing "$var1: Bool = false" + Then I should see output containing "$var1: Boolean = false" Scenario: Evaluating null Given I start the REPL diff --git a/src/Functions/evaluation.php b/src/Functions/evaluation.php index 8432908..5b7f886 100644 --- a/src/Functions/evaluation.php +++ b/src/Functions/evaluation.php @@ -23,6 +23,7 @@ use function Success; use function Failure; +use function Phunkie\Functions\type\normaliseType; /** * Pure function to evaluate a PHP expression. @@ -297,25 +298,25 @@ function evaluateNode(Node $node, ReplSession $session): Validation { return match (true) { $node instanceof Scalar\Int_ - => Success(EvaluationResult::of($node->value, 'Int')), + => Success(EvaluationResult::of($node->value, getType($node->value))), $node instanceof Scalar\Float_ - => Success(EvaluationResult::of($node->value, 'Float')), + => Success(EvaluationResult::of($node->value, getType($node->value))), $node instanceof Scalar\String_ - => Success(EvaluationResult::of($node->value, 'String')), + => Success(EvaluationResult::of($node->value, getType($node->value))), $node instanceof Scalar\InterpolatedString => evaluateInterpolatedString($node, $session), $node instanceof Expr\ConstFetch && $node->name->toString() === 'true' - => Success(EvaluationResult::of(true, 'Bool')), + => Success(EvaluationResult::of(true, getType(true))), $node instanceof Expr\ConstFetch && $node->name->toString() === 'false' - => Success(EvaluationResult::of(false, 'Bool')), + => Success(EvaluationResult::of(false, getType(false))), $node instanceof Expr\ConstFetch && $node->name->toString() === 'null' - => Success(EvaluationResult::of(null, 'Null')), + => Success(EvaluationResult::of(null, getType(null))), $node instanceof Expr\ConstFetch && $node->name->toString() === 'None' => Success(EvaluationResult::of(\None(), getType(\None()))), @@ -1330,22 +1331,22 @@ function evaluateFunctionCall(Expr\FuncCall $node, ReplSession $session): Valida /** * Gets the type name of a value. * + * Only the names the REPL invents for itself live here. Every native type is named + * by Phunkie's normaliseType(), so the console cannot drift away from the library. + * get_debug_type() is what we hand it: it spells native types the way normaliseType() + * expects, where gettype() would say "double" and "NULL". + * * @param mixed $value * @return string */ function getType(mixed $value): string { return match (true) { - is_null($value) => 'Null', - is_bool($value) => 'Bool', - is_int($value) => 'Int', - is_float($value) => 'Float', - is_string($value) => 'String', - is_array($value) => 'Array', $value instanceof \Closure => 'Callable', $value instanceof \Generator => 'Generator', is_object($value) => getObjectType($value), - default => 'Unknown' + is_resource($value) => normaliseType('resource'), + default => normaliseType(get_debug_type($value)), }; } @@ -2242,7 +2243,7 @@ function evaluateInstanceof(Expr\Instanceof_ $node, ReplSession $session): Valid // Perform the instanceof check $result = $object instanceof $className; - return Success(EvaluationResult::of($result, 'Bool')); + return Success(EvaluationResult::of($result, getType($result))); } catch (\Throwable $e) { return Failure(new EvaluationError( 'Instanceof', diff --git a/tests/Unit/Functions/EvaluationTest.php b/tests/Unit/Functions/EvaluationTest.php index ed11619..e6a5231 100644 --- a/tests/Unit/Functions/EvaluationTest.php +++ b/tests/Unit/Functions/EvaluationTest.php @@ -11,10 +11,12 @@ namespace Tests\Phunkie\Console\Functions; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Phunkie\Console\Types\ReplSession; -use function Phunkie\Console\Functions\{evaluateExpression, cleanErrorMessage}; +use function Phunkie\Console\Functions\{evaluateExpression, cleanErrorMessage, getType}; +use function Phunkie\Functions\type\normaliseType; class EvaluationTest extends TestCase { @@ -121,6 +123,63 @@ public function testEvaluateExpressionHandlesExpressionWithVariables(): void $this->assertEquals(15, $evalResult->value); } + /** + * @return list + */ + public static function scalarValues(): array + { + return [ + [null, 'null'], + [true, 'bool'], + [false, 'bool'], + [42, 'int'], + [3.14, 'float'], + ['hello', 'string'], + [[1, 2, 3], 'array'], + ]; + } + + #[DataProvider('scalarValues')] + public function testGetTypeTakesItsScalarNamesFromPhunkie(mixed $value, string $nativeType): void + { + $this->assertSame(normaliseType($nativeType), getType($value)); + } + + public function testGetTypeNamesBooleansBoolean(): void + { + $this->assertSame('Boolean', getType(true)); + $this->assertSame('Boolean', getType(false)); + } + + public function testGetTypeNamesClosuresCallable(): void + { + $this->assertSame('Callable', getType(fn(int $x): int => $x)); + } + + public function testGetTypeNamesGenerators(): void + { + $generator = (function () { + yield 1; + })(); + + $this->assertSame('Generator', getType($generator)); + } + + public function testGetTypeDelegatesObjectsToTheirShowType(): void + { + $this->assertSame('Option', getType(Some(42))); + $this->assertSame('List', getType(ImmList(1, 2, 3))); + } + + public function testEvaluatingBooleanLiteralsYieldsBoolean(): void + { + $session = ReplSession::empty(); + + $this->assertSame('Boolean', evaluateExpression('true', $session)->getOrElse(null)->type); + $this->assertSame('Boolean', evaluateExpression('false', $session)->getOrElse(null)->type); + $this->assertSame('Boolean', evaluateExpression('1 < 2', $session)->getOrElse(null)->type); + } + public function testCleanErrorMessageIsAvailable(): void { $message = 'TypeError: Too few arguments to function bind(), 0 passed in /Users/md/code/phunkie/console/src/Repl/ReplLoop.php(470) : eval()\'d code on line 4 and exactly 1 expected';