Bug #9877
output extent parameter when the type is object
100%
Related issues
History
#2 Updated by Constantin Asofiei over 1 year ago
- Assignee set to Paul Bodale
There is this example which uses a different 4GL object type at the argument and the parameter:
def var lo as progress.lang.object extent 5. function funco returns progress.lang.class extent 5 (output o as Progress.Lang.Class extent 5). return funco(output o). end. funco(output lo).
In FWD, this gets converted to this, and it does not compile:
funco(new OutputExtentParameter<object<? extends com.goldencode.p2j.oo.lang._BaseObject_>>()
{
public object<? extends com.goldencode.p2j.oo.lang._BaseObject_>[] getVariable()
{
return lo;
}
public void setVariable(final object<? extends com.goldencode.p2j.oo.lang._BaseObject_>[] newRef)
{
lo = newRef;
}
});
The correct conversion should be:
funco(new OutputExtentParameter<object<? extends com.goldencode.p2j.oo.lang.LegacyClass>>()
{
public object<? extends com.goldencode.p2j.oo.lang.LegacyClass>[] getVariable()
{
return (object[]) lo;
}
public void setVariable(final object<? extends com.goldencode.p2j.oo.lang.LegacyClass>[] newRef)
{
lo = newRef;
}
});
Note how the generic type used (at the method call OutputExtentParameter) is the one specified at the parameter, and not the argument's. And also the object[] cast in getVariable.
#3 Updated by Paul Bodale over 1 year ago
- Status changed from New to WIP
I tried to play around with this but I wasn't able to find a case where this would not compile.
The basic example I worked with is this:
DEFINE VARIABLE obj AS Progress.Lang.Object EXTENT 3. DEFINE VARIABLE res AS Progress.Lang.Object EXTENT 3. FUNCTION f RETURNS Progress.Lang.Object EXTENT 3 (OUTPUT out AS Progress.Lang.Object EXTENT 3): DEFINE VARIABLE obj AS Progress.Lang.Object EXTENT 3. RETURN obj. END FUNCTION. res = f(OUTPUT obj).
which converts as described here
In FWD, this gets converted to this, and it does not compile:
[...]
but it compiles just fine.
Until further feedback I'll look into the functionality of this to make sure that the extent OUTPUT parameter in and after the function call, behaves as expected.
#4 Updated by Constantin Asofiei over 1 year ago
Are you compiling with Java 8 or Java 17?
#5 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Are you compiling with Java 8 or Java 17?
Java 17.
openjdk 17.0.14 2025-01-21 OpenJDK Runtime Environment (build 17.0.14+7-Ubuntu-124.04) OpenJDK 64-Bit Server VM (build 17.0.14+7-Ubuntu-124.04, mixed mode, sharing)
#6 Updated by Constantin Asofiei over 1 year ago
OK, well, your signature at the function definitions matches the argument's. define the argument obj as progress.lang.class, not progress.lang.object.
#7 Updated by Paul Bodale over 1 year ago
- % Done changed from 0 to 30
Constantin Asofiei wrote:
OK, well, your signature at the function definitions matches the argument's. define the argument
objasprogress.lang.class, notprogress.lang.object.
Thanks, I understand now.
It looks like the problem comes from the annotations/output_parameters.rules, specifically this place:
<rule>!evalLib("fieldtype", type)
<action>refid = getNoteLong("refid")</action>
...
Here the refid of the function declaration ast gets overridden with the id of the ast representing the DEFINE_VARIABLE statement for the variable given as argument to the function call.
I'm currently working on finding a solution.
#8 Updated by Paul Bodale over 1 year ago
Quick question, when converting extent variables given as arguments what is the node that is generally taken into account, the variable definition or the function definition?
I want to take into account how fixing this issue might affect existing functionality.
#9 Updated by Paul Bodale over 1 year ago
Normally those definitions should be the same or else there would be a compilation error. But it is much more complicated with objects, because while they could have a certain type, they could be compatible if for example they implement the same interface or inherit the same class.
Besides this, the example shows that a instance of a class that inherits another class (like progress.lang.object) can even be replaced with one of its parents and not with children like any other normal programming languages (except JavaScript and Python which are dynamically typed). In other words it should not be possible to pass a instance of a class to a function that expects one of a another class for the case when the class expected is a child of the class given yet progress allows this (at least for the object class).
#10 Updated by Constantin Asofiei over 1 year ago
Quick question, when converting extent variables given as arguments what is the node that is generally taken into account, the variable definition or the function definition?
You can create some tests with integer[] vs decimal[]. I don't have a quick answer, but what I know is that the argument and parameter type must match depending on the mode (INPUT/OUTPUT/INPUT-OUTPUT). So, for OUTPUT, the argument's type needs to be wider than the parameter's type (as the argument will be written).
- OUTPUT means the argument's type is wider than the parameter's type (so arg can be
progress.lang.objectwhile parameterprogress.lang.class) - for INPUT means the argument's type is narrower than the parameter's type (so arg can be
progress.lang.classwhile parameterprogress.lang.object) - for INPUT-OUTPUT, they must match exactly AFAIK.
#11 Updated by Paul Bodale over 1 year ago
Ok so, I tried examples with integer[] and decimal[] but the converted code won't compile. That is because of the wrap function but that's another problem that doesn't have to do with this task.
I tried writing another simple example with a class that inherits another and it looks like the converted code has the same problem:
DEFINE VARIABLE b AS CLASS task.ClsB EXTENT 3. FUNCTION func RETURNS INTEGER (OUTPUT out AS task.ClsA EXTENT 3): END FUNCTION. func(OUTPUT b).
Converts to:
@LegacySignature(type = Type.MAIN, name = "task/play.p")
public void execute()
{
externalProcedure(Play.this, new Block((Body) () ->
{
func(new OutputExtentParameter<object<? extends com.goldencode.testcases.task.ClsB>>()
{
public object<? extends com.goldencode.testcases.task.ClsB>[] getVariable()
{
return b;
}
public void setVariable(final object<? extends com.goldencode.testcases.task.ClsB>[] newRef)
{
b = newRef;
}
});
}));
}
I assume that the correct version would be this:
@LegacySignature(type = Type.MAIN, name = "task/play.p")
public void execute()
{
externalProcedure(Play.this, new Block((Body) () ->
{
func(new OutputExtentParameter<object<? extends com.goldencode.testcases.task.ClsA>>()
{
public object<? extends com.goldencode.testcases.task.ClsA>[] getVariable()
{
return (object<? extends ClsA>[]) b;
}
public void setVariable(final object<? extends com.goldencode.testcases.task.ClsA>[] newRef)
{
b = newRef;
}
});
}));
}
I don't think that this is going to be as easy as changing the deftype annotation from "com.goldencode.p2j.oo.lang._BaseObject_" to "com.goldencode.p2j.oo.lang.LegacyClass" in the annotations/output_parameters.rules file. We might need to make a whole different template/library specifically for when the arguments/parameters are objects.
I tried looking into how the graft is made in the
TemplateWorker class but it is going to take me a while to understand what's happening there and also someone willing to explain to me. Got any advice?#12 Updated by Constantin Asofiei over 1 year ago
The conversion is done at convert/output_parameter.rules around line 138.
In annotations/output_parameter.rules, save an additional outputExtentObjectType annotation (from the target parameter definition, like object<? extends ...>). From there, in convert/output_parameter.rules, if outputExtentType starts with object<, and it does not match the outputExtentObjectType annotation, then use a different template - see java_templates.tpl, like output_extent_var_expr.
#13 Updated by Paul Bodale over 1 year ago
Committed rev. 15858 on branch 9877a. This version resolves the initial issue but I'm not sure it converts correctly in all cases.
For example:
DEFINE VARIABLE b AS CLASS task.ClsB EXTENT 3. FUNCTION func RETURNS INTEGER (OUTPUT out AS task.ClsA EXTENT 3): END FUNCTION. func(OUTPUT b).
Where ClsA inherits ClsB, converts to:
public class Play
{
@LegacySignature(type = Type.VARIABLE, extent = 3, name = "b")
object<? extends com.goldencode.testcases.task.ClsB>[] b = UndoableFactory.objectExtent(3, com.goldencode.testcases.task.ClsB.class);
/**
* External procedure (converted to Java from the 4GL source code
* in task/play.p).
*/
@LegacySignature(type = Type.MAIN, name = "task/play.p")
public void execute()
{
externalProcedure(Play.this, new Block((Body) () ->
{
func(new OutputExtentParameter<object<? extends com.goldencode.testcases.task.ClsA>>()
{
public object<? extends com.goldencode.testcases.task.ClsA>[] getVariable()
{
return (object[]) b;
}
public void setVariable(final object<? extends com.goldencode.testcases.task.ClsA>[] newRef)
{
b = newRef;
}
});
}));
}
@LegacySignature(type = Type.FUNCTION, name = "func", returns = "INTEGER", parameters =
{
@LegacyParameter(name = "out", type = "OBJECT", extent = 3, qualified = "task.clsa", mode = "OUTPUT")
})
public integer func(final OutputExtentParameter<object<? extends com.goldencode.testcases.task.ClsA>> extout)
{
object<? extends com.goldencode.testcases.task.ClsA>[] out = TypeFactory.initOutput(extout, 3);
return function(this, "func", integer.class, new Block());
}
}
Is the cast to object[] correct? Or should it be object<? extends ClsA>[]?
#14 Updated by Paul Bodale over 1 year ago
- % Done changed from 30 to 60
#15 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Is the cast to
object[]correct? Or should it beobject<? extends ClsA>[]?
Does it compile in Java 8 (and Java 17) if you manually change to object<? extends ClsA>[] ?
I've looked at the branch and the approach is good; the only note: if you look at the function def AST, there will be a typelist annotation - get the type from there. See annotations.xml line 794 for an example how is used.
Also, please test with OO method calls, too.
#16 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Paul Bodale wrote:
Is the cast to
object[]correct? Or should it beobject<? extends ClsA>[]?Does it compile in Java 8 (and Java 17) if you manually change to
object<? extends ClsA>[]?
I tried both tests (the one with the custom classes and the one with progress.lang.Object/Class) with the modification on both java 17 and java 8 and they compile and run except for the problem described below.
I've looked at the branch and the approach is good; the only note: if you look at the function def AST, there will be a
typelistannotation - get the type from there. Seeannotations.xmlline 794 for an example how is used.
I need to have the parameter ast because I'm using the "full-java-class" annotation to build the bounding for the object> wildcard. This is also how the outputExtentType annotation is created. But I've noticed a problem with this, if the function returns an extent then the first child won't be the LPARENS like I expect. Is there maybe a function in common-progress.rules that can help with this?
#17 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Also, please test with OO method calls, too.
I wrote a batch of tests and after some problems I managed to get them converted and compiled successfully.
- I believe this is a known issue but one of the problems were because I didn't qualify the argument given to the function call as being OUTPUT. This resulted in wrong conversion.
- Another problem I had was that the rule I wrote was supposed to accommodate method calls too. I added a function in
common-progress.rulescalledget_param_astto be able to retrieve the corresponding parameter of a function or a method call. - The tests I wrote include the cases for integer/decimal as well as coverage for some known issues I had. There seems to be a problem with the behavior of BDT extents given as output arguments for method calls. It looks like the variables don't get updated and stay the same as before the function call.
- There are 2 simple cases where calling a function that expects a integer extent with a decimal extent doesn't work because of the wrap function but this is not part of this task.
Other than the exceptions related to BDT variables/parameters mentioned, the tests convert correctly and are passing successfully.
#18 Updated by Paul Bodale about 1 year ago
I tested with both java 8 and java 17 and in both cases the project compiles and runs.
I committed rev. 15861 with the clean and complete fix and added history entries.
Standing by for review 😄
#19 Updated by Paul Bodale about 1 year ago
- Status changed from WIP to Review
- % Done changed from 60 to 90
#20 Updated by Constantin Asofiei about 1 year ago
Paul, just one question: get_param_ast - this is supposed to find a PARAMETER node under LPARENS of the method/function definition, right? I ask because for calls, the arguments are not parented in a PARAMETER node, so the argument index is the param_index annotation, not indexPos.
#21 Updated by Paul Bodale about 1 year ago
Constantin Asofiei wrote:
Paul, just one question:
get_param_ast- this is supposed to find a PARAMETER node underLPARENSof the method/function definition, right?
Yes.
I ask because for calls, the arguments are not parented in a
PARAMETERnode, so the argument index is theparam_indexannotation, notindexPos.
I used the param_index annotation when calling the function from annotations/output_parameters.rules. The arguments of the get_param_ast are the function/method node (which is the parent in our case) and the index of the parameter which is retrieved via #(int) getNoteLong("param_index").
Perhaps I used indexPos in a older revision and realized the mistake after. The latest revision is 15861 .
#22 Updated by Constantin Asofiei about 1 year ago
- Status changed from Review to Internal Test
We can to put this into conversion testing, the review for 15861 is OK. Anything else to do for this task? If not, please update the %Done.
#23 Updated by Constantin Asofiei about 1 year ago
- Related to Bug #9923: extent argument type vs parameter type compatibility added
#24 Updated by Paul Bodale about 1 year ago
- % Done changed from 90 to 100
Committed rev. 15872 to branch 9877a. This revision fixes a problem found during conversion testing where the code would try to call the method equals when the annotation didn't exist.
#25 Updated by Paul Bodale about 1 year ago
Committed rev. 15873 on branch 9877a which fixes the issue where the code would convert to the text ${rettype}.
Took me a little to reproduce the error, it was because of a mistake in the rule introduced by the previous commit.
#26 Updated by Constantin Asofiei about 1 year ago
- Status changed from Internal Test to Merge Pending
Conversion testing passed - do not merge now. I will post when this can be merged.
#27 Updated by Constantin Asofiei about 1 year ago
Please merge 9877a to trunk now.
#28 Updated by Paul Bodale about 1 year ago
- Status changed from Merge Pending to Test
Branch 9877a was merged into trunk as rev. 15886 and archived.