-
Notifications
You must be signed in to change notification settings - Fork 16
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
Add support for more types #51
Conversation
(some tests need to be adjusted)
👋 Welcome back mcimadamore! A progress list of the required criteria for merging this PR into |
@mcimadamore This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 11 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
@mcimadamore this pull request can not be integrated into git checkout projections
git fetch https://git.openjdk.org/babylon.git code-reflection
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge code-reflection"
git push |
Type quotedReturnType = new ClassType(null, | ||
com.sun.tools.javac.util.List.of(quotedOpType), syms.quotedType.tsym); | ||
MethodType mtype = new MethodType(nil, quotedReturnType, nil, syms.methodClass); | ||
MethodType mtype = new MethodType(nil, syms.quotedType, nil, syms.methodClass); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code seemed to try to parameterized the Quoted
type, which is no (longer) a generic type. This was causing a crash in the logic for computing the set of captured variables of a given type (types::captures
).
This change is what caused the fixes in the two reflect/code tests, as the tests were also expecting a parameterized Quoted type.
*/ | ||
public final class TypeVarRef implements JavaType { | ||
|
||
// @@@: how do we encode tvar owner? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the comment indicates, ideally a type-variable reference should also points to its owner (a type or a method). I'm not 100% sure how to encode that in the TypeElement
structure (see also the toplevel PR summary).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now handled as part of 52fc6e9
Tweak type projection to eliminate intersections/unions
Tweak tests Add erasure method
Webrevs
|
@@ -2236,22 +2240,15 @@ FieldRef symbolToFieldRef(Symbol s, Type site) { | |||
// @@@ Made Gen::binaryQualifier public, duplicate logic? | |||
// Ensure correct qualifying class is used in the reference, see JLS 13.1 | |||
// https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1 | |||
return symbolToFieldRef(gen.binaryQualifier(s, types.erasure(site))); | |||
return symbolToErasedFieldRef(gen.binaryQualifier(s, types.erasure(site))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized there was an issue here, as the field reference was not using erased types, and so it was incompatible with the binary qualifier used in codegen
while (l.acceptIf(Tokens.TokenKind.DOT)) { | ||
identifier.append(Tokens.TokenKind.DOT.name); | ||
t = l.accept(Tokens.TokenKind.IDENTIFIER); | ||
identifier.append(t.name()); | ||
} | ||
|
||
if (l.token().kind == TokenKind.COLCOL && isTypeVar) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we see #Foo::Bar
we might be seeing two things:
- a type-variable
Bar
in classFoo
- a method type-variable in some method
Foo.Bar
So, we need to disambiguate based on what follows. E.g. if after Bar
we see (
, then we know we're in the method case.
t = l.accept(TokenKind.IDENTIFIER); // type-var or method name | ||
identifier.append(t.name()); | ||
if (l.token().kind == TokenKind.LPAREN) { | ||
FunctionType functionType = parseMethodType(l); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that here we parse, then we throw away, as the type definition only wants a string-based identifier, so we'll need to reparse the identifier string again in the type factory.
if (typeArguments.size() != 1) { | ||
throw new IllegalArgumentException("Bad type-variable bounds: " + tree); | ||
} | ||
String[] parts = identifier.split("::"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this is the duplicate parsing logic (although here we already know if it's a method or a class type-variable based on the number of ::
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if instead we can check #
, and the parsers job is dumbly accumulate all valid characters (selected tokens and identifiers) up to but not including the <
token. We could even check if there is quoted string for the type identifier.
Note the special code for arrays in the parser was added only to avoid updating many tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can take a look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uploaded a new iteration with this simplification (which looks much nicer than what I had):
Note that if we wanted a truly general "quoting" mechanism we'd need both a prefix and a suffix token. Otherwise one can only use quotes if there's some nested type-definition with <>
. Your idea of using just strings (e.g. surrounded with "
) seems a powerful one (and more robust in the long run), because it would make the desc parsing logic a lot less opinionated (e.g. we wouldn't even need to special case qualified identifiers).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's much simpler. We can iterate further afterwards if need be. I believe you can now replace identifier.contains("::")
with identifier.startsWith("#")
?
From a modelling perspective, it would be cleaner to have a But doing this in the current world is painful: all types have a uniform structure (identifier + list of type elements), which pushes us towards modelling wildcards using proper types (otherwise parsing becomes very convoluted). To be honest, with the recent changes to |
I really like core principle of projecting upwards to a (or the nearest?) denotable supertype. It really simplifies things and is generally easy to grasp, even if the actual details can be hard to understand e.g., the set of Java types expressible in the code model is almost the same as the set of the types one can express in source code. I agree with you having a clearer distinction for modeling type arguments, it may be useful to have a top-level Java type'ish interface covering Java type and java type argument. This seems possible, the Java type factory can create whatever instances it wants based off the type identifier information e.g.,
? |
Yes, |
Right, this enables us to serialize and parse non-Java-based code models with some non-Java-like type descriptions. |
1309ab7
to
89a551a
Compare
@mcimadamore Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. See OpenJDK Developers’ Guide for more information. |
I gave this a try, but I don't think we should pursue this, at least not as part of this patch. Here's some code I put together: I think I got the parser working, but then we're greeted with a death-by-thousands cuts situation where most classes use This is aggravated by the fact that there's no type to say "a JavaType that is also a type argument". As a result, Overall it wasn't clear to me that doing this refactoring would be beneficial, especially as part of a PR that is already relatively big - given that the refactoring doesn't seem the "slam dunk" we were looking for.
|
if (typeArguments.size() != 1) { | ||
throw new IllegalArgumentException("Bad type-variable bounds: " + tree); | ||
} | ||
String[] parts = identifier.split("::"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's much simpler. We can iterate further afterwards if need be. I believe you can now replace identifier.contains("::")
with identifier.startsWith("#")
?
/integrate |
Going to push as commit 6713aca.
Your commit was automatically rebased without conflicts. |
@mcimadamore Pushed as commit 6713aca. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This PR adds support for type-variables and wildcard type arguments in the code model
JavaType
's hierarchy.This allows the code model to reflect the source types much more accurately, as we no longer need to erase the source type at the first sign of a non-denotable type. Instead, we can use the a modified (see below) version of the
Types::upwards
function (type projection) to compute the closest denotable upper bound to the type found in the source code. This means that the type associated with every op in the model is a (denotable) supertype of the type in the javac AST. The fact that such type is denotable has three important consequences:j.l.r.Type
(not implemented in this PR), as explained belowSome parser changes were required to support this, so that we can serialize and deserialize the new types accordingly.
A new method has been added to
JavaType
, namelyJavaType::erasure
, which computes the erasure of aJavaType
. This might come in handy when lowering the model into bytecode. Since supporting erasure is crucial, modelling of types has been carefully chosen as to facilitate that operation as much as possible: that is why, for example,TypeVariableRef
contains the "principal" type-variable bound (so that we can define erasure for type-variables in a straightforward fashion, as the erasure of the primary bound).Denotable projections
The code model type associated with an op result is computed by applying a modified version of
Types::upwards
- that is, the function that implements type projections as specified in JLS 4.10.5. The original projection algorithm is designed to leave intersection types in place - while this is handy, as it maximizes the applicability of the type inferred for local variables declared withvar
, for the code model use this is not suitable, as we'd like to get to a denotable type in the end (jshell has a similar problem, which was addressed in a more ad-hoc way).It is generally possible to project an intersection type using only one of its bounds, e.g.
Is projected to:
There are, however, problems when projecting intersection types that are on the right of some lower-bounded wildcard - e.g.
In this case, projecting to
List<? super A>
is not valid, asList<? super A>
is not a supertype ofList<? super A & B>
. For this reason, in these cases we have to fallback to an unbounded wildcardList<?>
.Runtime resolution
Support for runtime resolution of elements in the
JavaType
hierarchy is possible, as there is a subtype ofj.l.r.Type
for each of the subtypes inJavaType
. The main problem is being able to resolve type-variables: in the current modelling, type-variable types only have a name, and names can be ambiguous. That is, it could be possible for a type-variable with same name to be defined at different levels in the source code:To allow for better disambiguation we need to add ownership information to the
TypeVariableRef
class. This could point to either anotherJavaType
(if the type-variable is a class type-variable), or to aMethodRef
in case the type-variable is defined in a method. In this PR I didn't want to tackle to problem of modelling this additional information (that will come in a follow-up PR). Once the proper ownership info is in place, we might add code to enable runtime resolution ofJavaType
s.Update
After some consideration, I have also added support for ownership info in type-variables. A type variable reference can now have a parent method or class (the source element which declared the type-variable). In the former case, a
MethodRef
is used, in the latter case aJavaType
is used. The string representation for type-variables is a tad convoluted. For class type-variables:While for method type-variables:
The parser has been adjusted accordingly.
Progress
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/babylon.git pull/51/head:pull/51
$ git checkout pull/51
Update a local copy of the PR:
$ git checkout pull/51
$ git pull https://git.openjdk.org/babylon.git pull/51/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 51
View PR using the GUI difftool:
$ git pr show -t 51
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/babylon/pull/51.diff
Webrev
Link to Webrev Comment