Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce lowercase-string #3438

Open
wants to merge 23 commits into
base: 1.12.x
Choose a base branch
from

Conversation

VincentLanglet
Copy link
Contributor

No description provided.

Comment on lines 40 to 44
if (count($args) === 0) {
if (count($args) < 2) {
return null;
}

if (count($args) >= 2) {
Copy link
Contributor

@staabm staabm Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cleanup regarding count-of-args and the indent change over the whole block could be a separate PR to make this diff here more readable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened #3439

@staabm
Copy link
Contributor

staabm commented Sep 13, 2024

edge-case which came to mind while looking at the PR:

offset-lookup in a lower-case string yields a lower-case string:
https://3v4l.org/1eWRH

@VincentLanglet
Copy link
Contributor Author

There is at least

src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php
src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php
src/Type/Php/StrPadFunctionReturnTypeExtension.php
src/Type/Php/StrRepeatFunctionReturnTypeExtension.php
src/Type/Php/StrTokFunctionReturnTypeExtension.php

wich can be improved too, but maybe it's better in another PR.

@thg2k
Copy link
Contributor

thg2k commented Sep 13, 2024

What's the use case? Is there an issue linked explaining why we need this? Why not also uppercase string?

@VincentLanglet
Copy link
Contributor Author

VincentLanglet commented Sep 13, 2024

Why not also uppercase string?

lowercase-string is the only one supported by Psalm so far and codebase more often use lowercase-string than uppercase string. https://psalm.dev/r/c4d35bbe26

You can see it's used a lot in their codebase https://github.com/search?q=repo%3Avimeo%2Fpsalm%20lowercase-string&type=code
But it's also used in some others codebase like https://github.com/search?q=repo%3ARoave%2FBetterReflection%20lowercase-string&type=code (and certainly more).

It's useful

  • For case-insensitive mapping/behavior
  • When working with case insensitive data, like URL.

I personally work a lot with URL recently, and it's useful to unsure/checks that I always work with lowercase string.

@staabm
Copy link
Contributor

staabm commented Sep 13, 2024

Use separate test files in the nsrt/ folder and use a different // lint comment condition in the first line and PhpVersion method for the version check in the impl

@VincentLanglet VincentLanglet marked this pull request as ready for review September 13, 2024 14:36
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@VincentLanglet
Copy link
Contributor Author

I think it will be easier and better to have

src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php
src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php
src/Type/Php/StrPadFunctionReturnTypeExtension.php
src/Type/Php/StrRepeatFunctionReturnTypeExtension.php
src/Type/Php/StrTokFunctionReturnTypeExtension.php

in another PR.

@staabm
Copy link
Contributor

staabm commented Sep 13, 2024

I think this should include str-concatenation of 2 lowercase strings, because its a super low level thing

@VincentLanglet
Copy link
Contributor Author

I think this should include str-concatenation of 2 lowercase strings, because its a super low level thing

Indeed, it's supported now.

@staabm
Copy link
Contributor

staabm commented Sep 14, 2024

I think we are missing a rule test for e.g.

/** @param lowercase-string $s */
function doFoo($s) {
  if ($s === 'AB') {}
  if ('AB' === $s) {}
  if ('AB' !== $s) {}
  if ($s === 'ab') {}
  if ($s !== 'ab') {}
  if ($s === 'aBc') {}
  if ($s !== 'aBc') {}
  if ($s === '01') {}
  if ($s === '1e2') {}
  if ($s === MyClass::myConst) {}
  if ($s === A_GLOBAL_CONST) {}
  if (doFoo('hi') === 'AB') {}
}

which asserts rule error(s) when comparing constant with lowercase-string.


as a followup it also can be useful to test comparisons for string-functions

/** @param lowercase-string $s */
function doFoo($s, int $x, int $y) {
  $part = substr($s, $x, $y);
  if ($part === 'AB) {} // always false
}

new StringType(),
new AccessoryNonEmptyStringType(),
]);

case 'non-empty-lowercase-string':
Copy link
Contributor

@staabm staabm Sep 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a test somewhere for this type?
(I think I see it the very first time today)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 9636e83

@VincentLanglet
Copy link
Contributor Author

After this PR, we may want to introduce check for always false result
StrPosRule/StrStartsWithRule/StrContainRule/StrEndswithRule
cf https://phpstan.org/r/49d9f64f-9488-44ca-81e4-8d73767c4084

Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. There are no tests about accepting lowercase-string. Add a new test method to CallMethodsRuleTest and try passing different things like a constant string, another lowercase-string and general string to a parameter that accepts lowecase-string.
  2. This change is quite disruptive as you can see from the tests that needed changing. We should do this only for bleeding edge, similarly to the list type. This means that all the places that currently do new IntersectionType and add the new accesory type will need to do AccessoryLowercaseStringType::intersectWith() instead. Also, TypeNodeResolver will interpret lowercase-string type the new way only with bleeding edge.


public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
{
if ($type instanceof MixedType) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You copied this from accessory literal string type, but it does not belong here.

@@ -1129,19 +1129,19 @@ public function dataArrayDestructuring(): array
'$fourthStringArrayForeachList',
],
[
'string',
'lowercase-string',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get this change. IMHO it should remain a string.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe it should be numeric-string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'2018-12-19' is technically a lowercase-string.

And explode($lowercaseString) is array<lowercase-string>.

So every element of the array is precised from string to lowercase-string.

Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: We need excessive tests in TypeCombinatorTest::dataUnion and dataIntersect about the interaction with other string types.

@@ -296,6 +296,11 @@ public function isLiteralString(): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function isLowercaseString(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"10E4" is a numeric string not lowercase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is the reason I kept maybe

@ondrejmirtes
Copy link
Member

Maybe we don't need to do this only for bleeding edgw, just do a better job in VerbosityLevel::getRecommendedLevelByType

@VincentLanglet
Copy link
Contributor Author

Maybe we don't need to do this only for bleeding edgw, just do a better job in VerbosityLevel::getRecommendedLevelByType

Do you have a preferred solution between bleeding edge and changing getRecommendedLevelByType ?

I unsure to understand what is needed to change in getRecommendedLevelByType.

@ondrejmirtes
Copy link
Member

I'd like for lowercase-string to be in an error message about not-accepted type only if there's lowercase-string on the accepting side.

Please start by showing in a test how IntersectionType::describe() behaves with different VerbosityLevel when there's AccessoryLowercaseStringType involved.

I think we could bump it from value to precise.

And then in getRecommendedLevelByType we should look at $acceptedType and bump the returned level from value to precise only if $acceptingType->isLowecaseString()->yes().

After you do that, the impact on existing rules tests should be minimal. And I could merge it for everyone, not just on bleeding edge.

@VincentLanglet
Copy link
Contributor Author

I'd like for lowercase-string to be in an error message about not-accepted type only if there's lowercase-string on the accepting side.

Please start by showing in a test how IntersectionType::describe() behaves with different VerbosityLevel when there's AccessoryLowercaseStringType involved.

I think we could bump it from value to precise.

And then in getRecommendedLevelByType we should look at $acceptedType and bump the returned level from value to precise only if $acceptingType->isLowecaseString()->yes().

Ok, so If I understood correctly, it should be something like
1c20bb1

I need to update some tests, but still I have some issue with error message like

Strict comparison using === between string and 'AB' will always evaluate to false.

because not every Rules use VerbosityLevel::getRecommendedLevelByType, as an example

$addTip(RuleErrorBuilder::message(sprintf(
'Strict comparison using %s between %s and %s will always evaluate to false.',
$node->getOperatorSigil(),
$leftType->describe(VerbosityLevel::value()),
$rightType->describe(VerbosityLevel::value()),
)))->identifier(sprintf('%s.alwaysFalse', $node instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical'))->build(),

So I assume such thing need to be changed first...

@VincentLanglet
Copy link
Contributor Author

For testParsingDesiredTypeDescription and testDesiredTypeDescription the VerbosityLevel::value() is used.

Should I bump it to precise @ondrejmirtes ?

Also, do you want me to extract 053e6cd in another PR first ? I dunno if more rule will need the same change.

@staabm You're the one who suggested tests for this rule (#3438 (comment)) do you have more rule with lowercase-string in mind ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants