Add support for record pattern matching #5185
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR adds support for pattern matching with record patterns. The lowering is effectively done in 2 stages:
First, the
instanceof
check for the record pattern is created. This is represented as a series ofinstanceof
checks for the whole record tree. There are lots of examples of what this lowering looks like in the unit tests but, since the resulting lowering gets rather large very quickly, I'll include a simple example here.Record patterns with concrete + exact types
the
instanceof
check is lowered toThe first
o instanceof Foo
call is the same we'd expect for type pattern expressions.The next
(($obj1 = ((Foo) o).left()) instanceof String) && (($obj0 = ((Foo) o).right()) instanceof Integer)
introduces the major difference from type pattern matching. For each record pattern in the pattern tree, a temporary variable is created for each record field. In this case:This is necessary to avoid doubling-up on side-effects from repeating the getter calls in nested record patterns or in the pattern variable assignment.
The assignments for these temporary variables are added to the CPG where
((Foo) o).left()
, for example, is first called and anywhere after that((Foo) o).left()
is replaced by$obj1
.The assignment for the pattern variables are then
which look a bit silly in this case since we could've just used the
s
andi
variables directly instead of creating the temporary variables. This is because the return type of theleft
andright
calls already match the pattern variable types exactly. In cases where this is not true, however, the temporary variables are necessary and are cast to the correct types during the variable assignments as needed. This can be seen in the more complex example below.Records with generics / subtypes
where the instanceof check and assignments are respectively lowered to
Separately handling the simpler case where no temporary variables are technically needed would lead to other questions. For example, the current lowering
could be either simplified to
or the
instanceof
call could be omitted entirely. That would then lead to the question of when instanceof calls should even be added and I figured the extra code complexity added by dealing with this isn't justified by the results anyways (since this only applies to leaf patterns and we'd still need temporary variables for everything inbetween).Casts in instanceof chains for nested generics
Casts in general, but in particular in the instanceof calls, are only added when necessary. For example, in
the instanceof call is lowered to
where
(Foo) o
is the only cast needed since all the other types are already exact matches. However, if these types are not already exact matches as inthe instanceof call is lowered to
where the
(Bar) $obj0
cast is needed as well.