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

[Switch][Record Patterns] Unchecked conversion error missing from ECJ #3069

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

stephan-herrmann
Copy link
Contributor

  • try to balance responsibility about type checking between

    • Pattern.isApplicable() - don't yet report in nested position
    • RecordPattern.resolveType() - failed to check isApplicable()
    • InstanceOfExpression.resolveType() - avoid pattern-agnostic checks
  • added the 1st(!) test for inapplicable record pattern in switch

fixes #3066

@stephan-herrmann
Copy link
Contributor Author

@srikanth-sankaran please have a look at the different parties involved in compatibility checking for (record) patterns. Quite likely the code can be simplified by being more explicit about which part is responsible for which aspect of checking.

scope.problemReporter().incompatiblePatternType(this, other, patternType);
return false;
} else {
if (!checkCastTypesCompatibility(scope, patternType, other, null, true)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

see the swap of arguments?

@srikanth-sankaran
Copy link
Contributor

Thanks for the quick follow up Stephan. I'll review this Monday first thing - tomorrow is a holiday in India. Not just a regular holiday but one where you are not supposed to use any tools of your trade! 😊

(https://en.m.wikipedia.org/wiki/Ayudha_Puja)

And after I wrap up #2720 early next week I’ll join you in the grammar work in full earnest.

Copy link
Contributor

@srikanth-sankaran srikanth-sankaran left a comment

Choose a reason for hiding this comment

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

I am happy to look over once the current set of feedback is addressed.

Still feel it is a bit of a smell that we have in CaseStatement this:

if (expressionType != TypeBinding.NULL && !(e instanceof RecordPattern)) {

There is something in the flow that could be adjusted so we don't have to have this check for type test patterns alone - what obviates it for record patterns, why doesn't the same thing obviate it for non-record patterns too ? Don't know yet

@@ -277,6 +277,9 @@ public TypeBinding resolveType(BlockScope scope) {
if (expressionType == null || checkedType == null)
return null;

if (this.pattern instanceof RecordPattern) // subsequent tests are not relevant for RecordPatterns
return this.resolvedType = TypeBinding.BOOLEAN;

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks ok. However the short circuited code has a tendency to mischaracterize a type incompatibility as an unsafe cast. I have raised a separate ticket for that. See #3074

Copy link
Contributor Author

Choose a reason for hiding this comment

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

reworked in df50953 - do you like it better now?

return false;
}
applicable = false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The way this is coded it appears there could be a scenario where the cast is illegal but also unsafe. I doubt that to be valid ever.

If I change applicable = false; to be return applicable = false; I don't see any test failing.

If this is a valid observation, it could allow for some simplification of the code by we could get rid of the new variable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

reworked in df50953 - do you like it better now?

case X<String>(int x):
^^^^^^^^^^^^^^^^
Type X<String> cannot be safely cast to Object
----------
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks backwards - no ? The error is that Object cannot be safely cast to X and not the other way around ??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ups, right, we keep confusing ourselves by naming parameters as "right" and "left", which was ok-ish, while only one syntax was involved, but in pattern matching, where is right, where is left??
(ironically, I just fixed the same kind of bug a few lines up, and then introduced a new instanced of the bug here).
Next commit will include a bit of renaming.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bc53322

" ^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
"Incompatible conditional operand types R<capture#1-of ? extends I> and R\n" +
" ^^^^^^^^^^^^^\n" +
"Type mismatch: cannot convert from R<capture#1-of ? extends I> to R\n" +
Copy link
Contributor

Choose a reason for hiding this comment

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

This test doesn't compile with javac so unable to verify behavior - bug in javac

I get:

X.java:10: error: illegal start of type
        if (p instanceof R<>(String a)) {
                           ^
1 error

@stephan-herrmann
Copy link
Contributor Author

@srikanth-sankaran after my first commit focused on (local) correctness, in df50953 I made an attempt to define a basic rule for assigning responsibilities:

  • Pattern.isApplicable() is now responsible for most type checking of patterns
  • "clients" InstanceOfExpression and CaseStatement are now responsible for calling isApplicable() and integrating this call with there overall task of semantic analysis. Similarly for RecordPattern regarding nested patterns.

I could imagine that this can be taken even one step further. There are still differences between InstanceOfExpression and CaseStatement for which I don't readily see the reason, e.g., regarding integration with isReifiable() and handling of primitives.

I also slightly unified problem reporting: let the problem reporter select the problemID based on the error location rather than defining separate reporting methods. As one effect this allowed me to remove a reporting method, which was a bit sloppy with short vs long readable names.

I'm not yet 100% convinced of the idea to share IncompatibleTypesInEqualityOperator and IncompatibleTypesInConditionalOperator. Trying to clean up this area (overlap with checking for ConditionalExpression) also led to filing #3089

Let me know if you like better now, or if you see potential for further clarification.

@srikanth-sankaran
Copy link
Contributor

Yes, this is on my radar. I will review this before tomorrow. Thanks for your patience.

Copy link
Contributor

@srikanth-sankaran srikanth-sankaran left a comment

Choose a reason for hiding this comment

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

Looks good - except for the minor points called out.

leftShortName = leftName;
rightShortName = rightName;
int problemId = IProblem.IncompatibleTypesInEqualityOperator; // name is misleading, also used for instanceof patterns
if (location instanceof Pattern p && p.getEnclosingPattern() instanceof RecordPattern) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the comment about the name IncompatibleTypesInEqualityOperator being misleading valid ?? 4 lines down we seems to be overriding with IncompatibleTypesInConditionalOperator ??

It would seem the choice of emission of IncompatibleTypesInConditionalOperator by org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression.checkForPrimitives(BlockScope, TypeBinding, TypeBinding) via a call to org.eclipse.jdt.internal.compiler.problem.ProblemReporter.notCompatibleTypesError(Expression, TypeBinding, TypeBinding) is the source of confusion. See also the comment in PIPTSH.java

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right, the comment looks a bit stale, but the general question stays: is it appropriate to use problem ids designed for conditional expression and equality expression when reporting problems of pattern matching?

I'll remove the comment and raise a new issue for that discussion.

"3. ERROR in X.java (at line 9)\n" +
" case List<String> s: \n" +
" ^^^^^^^^^^^^^^\n" +
"This case label is dominated by one of the preceding case labels\n" +
"----------\n");
Copy link
Contributor

Choose a reason for hiding this comment

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

I gather this diagnostic going away is because of the early exit in CaseStatement ? If so, OK

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, when isApplicable() returns false, we've seen enough, resolveCasePattern() returns NotAConstant, hence no ResolvedCase is created and SwitchStatement.resolve() has nothing to check dominance for.

Incompatible conditional operand types X and int
----------
""");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I so much like javac's message : incompatible types: X cannot be converted to int - I don't know where this conditional operand types terminology originated but I am OK with that staying and perhaps being taken up separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

'conditional operand types' has been in use for instanceof expressions for at least very very long time (like: for ever?), see, e.g., org.eclipse.jdt.core.tests.compiler.regression.CastTest.test027()

I filed #3119 for follow-up.

+ try to balance responsibility about type checking between
  - Pattern.isApplicable() - don't yet report in nested position
  - RecordPattern.resolveType() - failed to check isApplicable()
  - InstanceOfExpression.resolveType() - avoid pattern-agnostic checks

+ added the 1st(!) test for inapplicable record pattern in switch
+ enclosing instanceof and case stmt are responsible for its invocation
+ make isApplicable() more capable to issue the desired problems
+ let problem reporter see the error location to select problemID
+ more renaming for clarity

Test changes:
+ compatibility errors more consistently mark the entire instanceof expr
+ remove unwanted type qualifiers
+ don't expect some secondary errors
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.

[Switch][Record Patterns] Unchecked conversion error missing from ECJ
2 participants