-
Notifications
You must be signed in to change notification settings - Fork 273
Feature/TG-494 Specialize Java Generic Methods #1460
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
Feature/TG-494 Specialize Java Generic Methods #1460
Conversation
2d731e7
to
9de05fe
Compare
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 looks really good. The main blocking change is the dependency on TG-865."Nits" are suggestions (or formatting) so feel free to disregard if you think you have a good reason not to.
// as the specialized method we are about to create. By decorating the | ||
// existing symbol, we ensure that no legal Java program could have already | ||
// defined the symbol. | ||
const std::string& signature_decoration=instantiation_decoration |
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.
Nit: break after the equals (or after the bracket)
(concrete_types); | ||
|
||
// Check the method has not already been specialized | ||
symbol_tablet::symbolst::const_iterator already_specialized= |
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.
Nit: As of this week(!) the symbol table's lookup method returns an optionalt
(you might need to rebase to get this) which probably makes this code a little simpler:
symbol_tablet::opt_const_symbol_reft already_specialised=symbol_table.lookup(...)
if(already_specialised)
return already_specialised.value();
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.
Actually, it looks like symbol_table.lookup has been further refactored even since then :-) so it now returns a symbolt*
, which returns nullptr
if the symbol isn't found. Point still stands though, and I'll update accordingly.
return already_specialized->second; | ||
|
||
// Convert concrete types into a map for faster lookups | ||
type_variable_instantiation_mapt concrete_type_map(concrete_types); |
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.
Two questions (that may become clear as I keep reading):
- why not just accept as input a map?
- faster lookup seems unlikely to have a big impact when I've never seen a class with more than 3 but I also suppose this will be more readable anway...
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.
The problem with just accepting a Map as the input is the lack of ordering. At the moment we decorate the method identifier with a list of the concrete types in the order they are provided in the initlializer_list - this means that we get a consistent ordering of types in the identifier decoration. e.g. if we have
class Foo <A, B> {
void someMethod(A a, B a) {...}
}
then we pass in an intializer_list that looks something like {{A,Integer},{B,Double}}
- which then gets used to decorate the method identifier with '<Integer, Double>' which matches the ordering of type parameters in the class definition. However, if we just passed in a Map<typevar,concretetype>
- we don't know whether we'd end up with '<Integer,Double>' or '<Double,Integer>', depending on the implementation of Map...
Of course, perhaps the correct answer is to use a Map, but instead of constructing the decoration string from the Map, go look up the parent class (or type parameters for generic methods in non-generic classes) to find the ordering of the type parameters.
|
||
// Handle a generic return type | ||
typet &method_return_type=specialized_code.return_type(); | ||
if(is_java_generic_parameter(method_return_type)) |
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 the case that a function returns T
right?
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.
Yep, thats right.
to_java_generic_parameter(method_return_type), | ||
concrete_type_map); | ||
} | ||
else if(is_java_generic_type(method_return_type)) |
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 case of List<T>
for example. Consider adding a comment to make this explicit
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.
Good point - I'll add some comments to make this clearer.
return be.elem; | ||
} | ||
|
||
class double_element<A,B> { |
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.
Nit: could you auto-format the code as it is a bit hard to figure out what is going on
class double_element<A,B> { | ||
A first; | ||
B second; | ||
java.util.Map<A,B> map; |
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 concerned using the library classes like Map
and List
as I suppose it is just stubbing them which may not be exactly what we want.
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.
Ah, good point - I'll update the tests to avoid standard container classes.
|
||
THEN("Specialize a method with a generic parameter types") | ||
{ | ||
REQUIRE( |
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 wonder if these "precondition" checks that are really checking the parsing works as you expect it to, should go in there own THEN
block to make it clear they aren't part of the testing specialisation stuff
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.
Yes, that probably makes sense - they act to document what the specialisation stuff is expecting as input, but that is, as you say, just testing the parsing is working.
"java::generics$double_element.insert:" | ||
"(Ljava/lang/Object;Ljava/lang/Object;)V"); | ||
|
||
const typet &symbol_type=generic_method_symbol.type; |
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.
Nit: a pattern I think might be good for us to use in our unit tests is something like this: https://github.com/diffblue/cbmc/blob/develop/unit/testing-utils/require_expr.cpp So for here you could do something like:
code_typet &require_code_type(const typet &type, optionalt<size_t> num_params, optional_t<typet &> return_type)
{
REQUIRE(type.id()==ID_code);
const code_typet &code_type=to_code_type(type);
if(num_params)
REQUIRE(code_type.parameters().size()==num_params.value());
// maybe check return type?
return code_type;
}
If you think it is a good idea you might like to use it here (and put it in testing-utils/require_type)
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 does look a neat way to cleanup the tests a bit - I'll have a play with that.
|
||
// FIXME: Eventually want to replace instantiations of | ||
// FIXME: generic types (e.g. List<Integer>) with references | ||
// FIXME: to concrete instantiations of those once PR1406 |
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 you could add a comment to the ticket to this effect as otherwise we might forget!
9de05fe
to
3eca6d9
Compare
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.
Looks good - blocking on the function signature/descriptor task and integrating Fotis's stuff into this.
A few formatting comments, they apply in more than one place.
/// \param concrete_type_map The mapping from type variable to concrete types. | ||
void generate_java_generic_methodt::instantiate_generic_types( | ||
typet &generic_type, | ||
const type_variable_instantiation_mapt |
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.
Nit: but the variable name on same line:
const type_variable_instantiation_mapt &concrete_type_map)
const type_variable_instantiation_mapt | ||
&concrete_types) | ||
{ | ||
INVARIANT(generic_parameter.id()==ID_pointer, |
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.
Formatting: break after (
INVARIANT(generic_parameter.subtype().id()==ID_symbol, | ||
"All generic parameters should point to symbols"); | ||
|
||
const symbol_typet &generic_type_variable=generic_parameter |
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.
Formatting: break after the .
|
||
const symbol_typet &generic_type_variable=generic_parameter | ||
.type_variable(); | ||
const auto &instantiation_type=concrete_types.find |
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.
Formatting: break after the (
or the =
second=b; | ||
} | ||
|
||
void setMap(java.util.Map<A,B> m) { |
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.
Outstanding: don't use java.util data structures
|
||
GIVEN("Some class files with Generics") | ||
{ | ||
WHEN("Methods with generic signatures") |
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.
Nit: I'd prefer if this was more specific WHEN("Generic method returning the generic type")
new_code.return_type(), {"java::java.lang.Integer"}); | ||
} | ||
} | ||
|
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 think it would be good to use a WHEN("Parsing a method that has a generic parameter")
} | ||
} | ||
|
||
{ |
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.
Ditto WHEN("Parsing a method with two generic parameters")
unit/testing-utils/require_type.cpp
Outdated
REQUIRE(generic_type_vars.size()==type_variables.value().size()); | ||
|
||
size_t i=0; | ||
for(irep_idt type_variable_identifier : type_variables.value()) |
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 think you can use something like:
REQUIRE(
std::equal(
type_variables->begin(),
type_variables->end(),
generic_type_vars.begin(),
[](const irep_idt ¶m_name, const generic_type_parametert ¶m)
{
REQUIRE(!is_java_generic_inst_parameter(param));
return param.type_variable().get_identifier()==param_name;
}));
to make it more clear(?)
{ | ||
INVARIANT(generic_parameter.id()==ID_pointer, | ||
"All generic parameters should be pointers in java"); | ||
INVARIANT(generic_parameter.subtype().id()==ID_symbol, |
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.
Let's discuss this tomorrow - I worry updating this as suggested to ns.follow(generic_type.subtype()).id()==ID_struct
will break your unit tests
cd1fb9c
to
ad82f9f
Compare
This reverts commit be94ecdd2caf2bf5193c2cd2ced1b1ae19ce22d9.
ad82f9f
to
e16286a
Compare
It appears this work will not be needed for the immediate generic support under development so I am closing this PR for now. Sorry again and thanks for the effort Chris. |
No description provided.