RFR: JEP 359-Records: compiler code

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Oct 21 18:01:58 UTC 2019


Hi Vicente,
I did a pretty thorough pass on most of the code. I didn't look at 
tests, and I also didn't look at Lower. Comments below:

* Flags.java - VARARGS flag for records components; I wonder, instead of 
a new flag, can we use the internal VARARGS flag we have for methods, 
and attach that to the record symbol? That should also lead to more 
direct code in TypeHelper

* Symbol.java - I think the override for 'erasure' is redundant - isn't 
that the impl from supertype?

* Symbol.java (and others) in general this webrev shuld be updated as 
soon as Jan push the @Preview work, as I see that methods implementing 
preview API are using the 'deprecate for removal' annotation

* Symbol.java - I wonder if accessor list with Pair<Kind, Symbol> isn't 
a premature generalization; we should just add a getter symbol and that's it

* Attr.java - I think we might want to leave the door open for a check 
which forces all constructors of a record to go through the canonical 
one (depending on where the spec lands)

* Check.java - understanding checkpoint: when we see an annotation on a 
record component, first we check it's one of the kinds which are allowed 
(if not, error), and, if it's allowed, we add all record component 
annotations to record component elements, and we also filter away all 
annotations that have nothing to do with the element in which they 
appear. If my understanding is correct, I think this logic should be 
documented more clearly; I found the comment after the "if 
(isRecordField)" to be a bit obscure.

* Enter.java - why are you removing the static flag on records? I don't 
see anything similar around for enums.

* Flow.java - not sure I get the changes to checkInit; typically 
checkInit is called at the use-site of DA/DU variables. Here it seems 
you suppress some of the errors emitted for accessing record fields 
inside the canonical constructor - but I hope that code like this

record Foo(int x) {
    Foo(int x) {
        print(this.x);
    }
}

Still give errors? If this works correctly, which errors does the 
'guard' around the error generation is supposed to protect against?

* MemberEnter.java - why the filter for HYPOTHETICAL ? It's only used 
here...

* TypeEnter.java - implicit super calls are added in Attr::visitMethod 
for regular calls; we should do the same for records (or add all in 
TypeEnter - that is records and class should be consistent)

* TypeEnter.java - on finishClass - you are calling memberEnter on 
record fields, which I think you already did in the new RecordsPhase

* TypeEnter.java - (stylistic) addRecordsMemberIfNeeded should deal with 
_all_ record members (e.g. including accessors), not just some?

* TypeEnter.java - checkForSerializationMember should probably be moved 
to MemberEnter::visitVar, or even to Attr (note that the code for the 
check is doing a little visit :-))

* TypeEnter.java - again on check timings; while it's ok for the code in 
here to add new synthetic members, I think it's less ok to add more 
global error checks (such as make sure that the canonical declaration 
whose parameter names match the record components in order); these 
should live in Attr. More generally, I think that we should only check 
stuff here if we think that the check will add any value to annotation 
processing. Every other check can be deferred, and take place in a more 
'deterministic' part of javac.

* TypeEnter.java - I think finishClass should be a bit better at 
determining as to whether default constructor is needed or not - for 
instance, this check:

if ((sym.flags() & INTERFACE) == 0 &&
  928                 !TreeInfo.hasConstructors(tree.defs)) {

Should be generalized to something that works for both classes and 
records; for classes you need to check if there's no other constructor; 
for records you need to check if there's no other constructor _with same 
signature_ as the canonical one. Then you can simplify addRecordMembers 
and remove the dependency on the boolean 'generatedConstructor' 
parameter. In other words the code should:

1) check if default/canonical constructor generation is required
2) if so, use the appropriate helper to generate the code
3) at the end, add the remaining record members (under the assumption 
that the canonical constructor has already been added in (1), if that 
was missing)

*TypeEnter.java - addAccessor can be simplified if we only worry about 
getters. Again, the checks in here feel more Attr check than MemberEnter 
checks.

*TypeEnter.java - in addRecordMembersIfNeeded, I don't get why we create 
a tree for a member, and then we visit the member tree with memberEnter, 
just to add it to the scope. I understand that, currently addEnumMembers 
does the same, but this looks very roundabout; I wonder if there's a way 
to make all this process a bit simpler - create a symbol and add that to 
the scope. Or are there important checks in MemberEnter that we would lose?

*JCTree.java/TreeMaker.java - I don't think there's any need to store 
accessors in the field AST; these are only used from TypeEnter, and 
TypeEnter can do whatever it does by looking at which record components 
there are in the record class, and add a getter for each. Let's make the 
code simpler and more direct

* ClassReader.java - should we just silently ignore record attributes 
when not in preview mode - or should we issue classfile errors?

* ClassReader.java - what kind of validation should we do on record 
attributes? Currently javac does nothing. Should we check that we have 
(i) getters (ii) toString/hashCode/equals implementations and (iii) a 
canonical constructor (ad fail if we don't) ? At the very least I would 
add code to _parse_ the attribute, even if we do nothing with it, so 
that at least we throw a classfile error if the attribute is badly broken

* Tokens.java - for "var", "yields" and other context-dependent keywords 
we never added a token. We just handled that in JavacParser. Why the 
difference here? I think it's best to stick to current style and maybe 
fix all of them (assuming that's what we want to do) in a followup 
cleanup. Actually, after looking at parser, it seems like you already 
handle that manually, so I just suggest to revert the changed to Tokens

* TreeInfo.java - how is 'isCanonicalConstructor' not returning 'true' 
for all constructors inside a record, as opposed to only return true for 
the canonical one?

* TreeInfo.java - There is some code reuse possible between 
"recordFieldTypes" and "recordFields"

* Names.java - what is 'oldEquals' ?

* JavacParser.java - timing of checks; I don't think we should check for 
illegal record component names in here

* JavacParser.java - code can be simplified somewhat by getting rid of 
accessors in VarDef AST.





On 21/10/2019 13:31, Vicente Romero wrote:
> Hi,
>
> Please review the compiler code for JEP 359 (Records) [1]
>
> Thanks in advance for the feedback,
> Vicente
>
> [1] 
> http://cr.openjdk.java.net/~vromero/records.review/compiler/webrev.00/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20191021/ad14b0a2/attachment-0001.html>


More information about the compiler-dev mailing list