Feature #6490
DYNAMIC-INVOKE which returns an extent
100%
Related issues
History
#1 Updated by Greg Shah about 4 years ago
- Related to Feature #4373: finish core OO 4GL support added
#2 Updated by Alexandru Lungu over 1 year ago
- Assignee set to Paul Bodale
#3 Updated by Constantin Asofiei over 1 year ago
DYNAMIC-INVOKE returning extent is tricky to implement, as we don't know at conversion-time what will return. This is the same for DYNAMIC-PROPERTY and DYNAMIC-FUNCTION.
For now, if the LVALUE is known (i.e. an extent prop/var), then we can infer from this, that DYNAMIC-INVOKE must return extent, and if it does not, then raise the proper ERROR condition.
But, if this is passed as an argument to a RUN or DYNAMIC-FUNCTION, or even another DYNAMIC-INVOKE, then things get complicated. There are lots of places which assume that these dynamic APIs return BDT and not an array.
#4 Updated by Paul Bodale over 1 year ago
- Status changed from New to WIP
- % Done changed from 0 to 30
From what I found so far, it looks like the EXTENT types are implemented in FWD with arrays. This has a implication for the implementation of DYNAMIC-INVOKE because it is supposed to be able to return anything a normal method can return.
After I traced the calls that are being made in the converted version I reached this function that applies a cast to BaseDataType to the returned object which has the generic type Object. This makes it impossible to return an EXTENT (or an array) value.
I will need to modify the ObjectOps class structure to accommodate this feature and adapt the code in other places where these functions are used as they are now. Is it ok to do so or is there another better solution?
#5 Updated by Alexandru Lungu over 1 year ago
After I traced the calls that are being made in the converted version I reached this function that applies a cast to BaseDataType to the returned object which has the generic type Object. This makes it impossible to return an EXTENT (or an array) value.
Before pursuing an implementation, please debug how static calls are resolved (aka not with dynamic-invoke, but straight-forward method invocations). Such knowledge can help understand what makes dynamic-invoke more trickier. I expect that the static invocation will also return an Object, but how will it manage to squeeze it in the return value? Does it also cast it to BaseDataType?
PS: Object x = new Object[]; is a correct syntax in Java. Anything is an Object, including native arrays. You can use x.getClass().isArray() to detect whether the actual Object is an array.
I will need to modify the ObjectOps class structure to accommodate this feature and adapt the code in other places where these functions are used as they are now. Is it ok to do so or is there another better solution?
When in doubt, please pinpoint some methods and pieces of code that you state that aren't "extent-compatible".
#6 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Greg, I would prefer to emit different APIs for cases like this:I will need to modify the
ObjectOpsclass structure to accommodate this feature and adapt the code in other places where these functions are used as they are now. Is it ok to do so or is there another better solution?
- if inferred from lvalue or the surrounding context that DYNAMIC-INVOKE (or DYNAMIC-PROPERTY even) must return an extent (i.e. the type is 'forced' by the 4GL compiler), then emit dedicated APIs for this, like
extentInvokeandextentProperty- which expect the result to be an array, but do not enforce it at compile time - instead, the runtime will check the result if is an array or not, and raise a proper ERROR condition if not. - otherwise, if we have a 'poly' case where the surrounding context can't determine if we expect an extent or not, then I would prefer to emit another specific API for this. The example I have in mind is:
def var lc as progress.lang.object. dynamic-property(lc, "somePropWhichCanBeExtent") = dynamic-invoke(lc, "someMethodWhichMayReturnExtent").
This case already hasObjectOps.setDynamicProperty(object extends _BaseObject_> ref, character prop, Object val), so we would just need anObjectOps.dynamicInvokereturning Object.
If we would have:
def var lc as progress.lang.object. def var i as int extent 5. i = dynamic-invoke(lc, "someMethod").
Then we know that dynamic-invoke must return extent so we can emit an ObjectOps.extentInvoke, which checks the result of the call if is extent or not (and if is not extent, then raise proper ERROR condition).
ControlFlowOps.invokeLegacyMethod already returns Object.
#7 Updated by Greg Shah over 1 year ago
I'm good with the plan. Instead of extentInvoke and extentProperty, let's use dynamicInvokeExtent and dynamicPropertyExtent so that it is more clear that this is a variant of DYNAMIC-INVOKE or DYNAMIC-PROPERTY respectively.
#8 Updated by Paul Bodale over 1 year ago
- % Done changed from 30 to 10
Alexandru Lungu wrote:
Before pursuing an implementation, please debug how static calls are resolved (aka not with dynamic-invoke, but straight-forward method invocations). [...] Does it also cast it to
BaseDataType?
Yes, I also traced a call to such a method and found that they get sent down a similar pipeline arriving in the same method of ControlFlowOps, invokeLegacyMethod.
The casting happens in the following methods of ObjectOps class:
- for instance methods:
public static BaseDataType invoke(object<? extends _BaseObject_> ref,
String mthdName,
String modes,
Object... args)
{
// TODO: extent?
return (BaseDataType) invoke(false, ref, mthdName, modes, args);
}
-for static methods:
public static BaseDataType invoke(String clsName, String mthdName, String modes, Object... args)
{
// TODO: extent?
return (BaseDataType) invoke(false, clsName, mthdName, modes, args);
}
The next functions that they invoke are returning an Object and the previous functions (the ones that invokes them) expect a BaseDataType.
I assume that I now have to look into TRPL to see exactly where the call is converted to ObjectOps.invoke and then figure out a way to transform that call to a new function extentInvoke when it's returning a extent.
#9 Updated by Greg Shah over 1 year ago
extentInvoke -> dynamicInvokeExtent
#10 Updated by Paul Bodale over 1 year ago
There is still a lot of work to do but I introduced limited support for the case when we can infer from the surrounding context that the result of the DYNAMIC-INVOKE must be an extent.
While testing the proposed solution I found this specific case for which it doesn't work:
If the assigned variable is an extent but the returned value is a BDT or the unknown value, the result should be that the extent will be filled with that value but this is not supported because I made the methods return an array of BaseDataType.
This puts us right back at the initial problem. Is it ok to change the return type of the functions I introduced to Object? Or how should I proceed?
#11 Updated by Alexandru Lungu over 1 year ago
There is still a lot of work to do but I introduced limited support for the case when we can infer from the surrounding context that the result of the DYNAMIC-INVOKE must be an extent.
Paul, please mention the branch name and rev. number when you are committing to it. This way, one can provide guidance directly based on the code committed. Otherwise, it is recommended to keep the code in the central repository in order to avoid loss of data from local stations - which never happened before AFAIK, but you never know :)
This puts us right back at the initial problem. Is it ok to change the return type of the functions I introduced to Object? Or how should I proceed?
Constantin mentioned that ControlFlowOps.invokeLegacyMethod already returns Object. and This case already has ObjectOps.setDynamicProperty(object ref, character prop, Object val), so we would just need an ObjectOps.dynamicInvoke returning Object., so I suspect you can do this change in other places to accommodate this single value / extent value duality.
#12 Updated by Constantin Asofiei over 1 year ago
Paul, please also post here some examples of 4GL code and how it converts (or should). I'm interested in variable assignment, property assignment, etc.
#13 Updated by Paul Bodale over 1 year ago
Alexandru Lungu wrote:
Paul, please mention the branch name and rev. number when you are committing to it. This way, one can provide guidance directly based on the code committed. Otherwise, it is recommended to keep the code in the central repository in order to avoid loss of data from local stations - which never happened before AFAIK, but you never know :)
I am referring to the branch #6490a, rev. 15667.
Constantin mentioned that
ControlFlowOps.invokeLegacyMethod already returns Object.andThis case already has ObjectOps.setDynamicProperty(object ref, character prop, Object val), so we would just need an ObjectOps.dynamicInvoke returning Object., so I suspect you can do this change in other places to accommodate this single value / extent value duality.
Thank you!
Constantin Asofiei wrote:
Paul, please also post here some examples of 4GL code and how it converts (or should). I'm interested in variable assignment, property assignment, etc.
I've only worked on the case where it can be inferred from the context that the method should return an EXTENT when I found that bug. For now, here is a simple example.
Class definition:
METHOD PUBLIC INTEGER testFuncUnknown ():
RETURN ?.
END METHOD.
METHOD PUBLIC STATIC INTEGER testSFunc2 (INPUT t AS INTEGER):
RETURN t + 2.
END METHOD.
METHOD PUBLIC STATIC INTEGER EXTENT 3 myStaticFunc1 ():
DEFINE VARIABLE myArr AS INTEGER EXTENT 3 INITIAL [1, 2, 3] NO-UNDO.
RETURN myArr.
END METHOD.
METHOD PUBLIC INTEGER EXTENT 3 myFunc2 (INPUT defInt AS INTEGER):
DEFINE VARIABLE myArr AS INTEGER EXTENT 3 NO-UNDO.
DEF VAR i AS INTEGER NO-UNDO.
DO i = 1 TO 3:
myArr[i] = defInt.
END.
RETURN myArr.
END METHOD.
Main procedure:
USING task.HelperClass.
DEFINE VARIABLE clsRef AS CLASS HelperClass.
clsRef = NEW HelperClass().
DEFINE VARIABLE res1 AS INTEGER EXTENT 3 NO-UNDO.
res1 = DYNAMIC-INVOKE(clsRef, "testFuncUnknown").
DISPLAY res1 WITH FRAME f1 NO-LABELS. // ? ? ?
DEFINE VARIABLE res2 AS INTEGER EXTENT 3 NO-UNDO.
res2 = DYNAMIC-INVOKE("task.HelperClass", "testSFunc2", 2).
DISPLAY res2 WITH FRAME f2 NO-LABELS. // 4 4 4
DEFINE VARIABLE res3 AS INTEGER EXTENT 3 NO-UNDO.
res3 = DYNAMIC-INVOKE("task.HelperClass", "myStaticFunc1").
DISPLAY res3 WITH FRAME f3 NO-LABELS. // 1 2 3
DEFINE VARIABLE res4 AS INTEGER EXTENT 3 NO-UNDO.
res4 = DYNAMIC-INVOKE(clsRef, "myFunc2", 2).
DISPLAY res4 WITH FRAME f4 NO-LABELS. // 2 2 2
And converted code:
package com.goldencode.testcases.task;
import com.goldencode.p2j.util.*;
import com.goldencode.testcases.ui.task.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.testcases.task.HelperClass;
import static com.goldencode.p2j.util.BlockManager.*;
import static com.goldencode.p2j.util.InternalEntry.Type;
import static com.goldencode.p2j.util.ArrayAssigner.*;
/**
* Business logic (converted to Java from the 4GL source code
* in task/p.p).
*/
public class P
{
PF3 f3Frame = GenericFrame.createFrame(PF3.class, "f3");
PF2 f2Frame = GenericFrame.createFrame(PF2.class, "f2");
PF1 f1Frame = GenericFrame.createFrame(PF1.class, "f1");
PF4 f4Frame = GenericFrame.createFrame(PF4.class, "f4");
@LegacySignature(type = Type.VARIABLE, name = "clsRef")
object<? extends HelperClass> clsRef = UndoableFactory.object(HelperClass.class);
@LegacySignature(type = Type.VARIABLE, extent = 3, name = "res1")
integer[] res1 = TypeFactory.integerExtent(3);
@LegacySignature(type = Type.VARIABLE, extent = 3, name = "res2")
integer[] res2// ? ? ?
= TypeFactory.integerExtent(3);
@LegacySignature(type = Type.VARIABLE, extent = 3, name = "res3")
integer[] res3// 4 4 4
= TypeFactory.integerExtent(3);
@LegacySignature(type = Type.VARIABLE, extent = 3, name = "res4")
integer[] res4// 1 2 3
= TypeFactory.integerExtent(3);
/**
* External procedure (converted to Java from the 4GL source code
* in task/p.p).
*/
@LegacySignature(type = Type.MAIN, name = "task/p.p")
public void execute()
{
externalProcedure(P.this, new Block((Body) () ->
{
f1Frame.openScope();
f2Frame.openScope();
f3Frame.openScope();
f4Frame.openScope();
clsRef.assign(ObjectOps.newInstance(HelperClass.class));
assignMultiPoly(res1, ObjectOps.dynamicInvokeExtent(clsRef, "testFuncUnknown"));
FrameElement[] elementList0 = new FrameElement[]
{
new Element(subscript(res1, 1), f1Frame.widgetRes1Array0()),
new Element(subscript(res1, 2), f1Frame.widgetRes1Array1()),
new Element(subscript(res1, 3), f1Frame.widgetRes1Array2())
};
f1Frame.display(elementList0);
assignMultiPoly(res2, ObjectOps.dynamicInvokeExtent("task.HelperClass", "testSFunc2", "I", 2));
FrameElement[] elementList1 = new FrameElement[]
{
new Element(subscript(res2, 1), f2Frame.widgetRes2Array0()),
new Element(subscript(res2, 2), f2Frame.widgetRes2Array1()),
new Element(subscript(res2, 3), f2Frame.widgetRes2Array2())
};
f2Frame.display(elementList1);
assignMultiPoly(res3, ObjectOps.dynamicInvokeExtent("task.HelperClass", "myStaticFunc1"));
FrameElement[] elementList2 = new FrameElement[]
{
new Element(subscript(res3, 1), f3Frame.widgetRes3Array0()),
new Element(subscript(res3, 2), f3Frame.widgetRes3Array1()),
new Element(subscript(res3, 3), f3Frame.widgetRes3Array2())
};
f3Frame.display(elementList2);
assignMultiPoly(res4, ObjectOps.dynamicInvokeExtent(clsRef, "myFunc2", "I", 2));
FrameElement[] elementList3 = new FrameElement[]
{
new Element(subscript(res4, 1), f4Frame.widgetRes4Array0()),
new Element(subscript(res4, 2), f4Frame.widgetRes4Array1()),
new Element(subscript(res4, 3), f4Frame.widgetRes4Array2())
};
f4Frame.display(elementList3); // 2 2 2.
}));
}
}
#14 Updated by Constantin Asofiei over 1 year ago
assignMultiPoly already has Object source parameter def. So the question is: where do you make the check that dynamicInvokeExtent should return an array or not? I think we need to make this check in assignMultiPoly, and in this case dynamicInvokeExtent can return Object.
#15 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
assignMultiPolyalready hasObject sourceparameter def. So the question is: where do you make the check thatdynamicInvokeExtentshould return an array or not? I think we need to make this check inassignMultiPoly, and in this casedynamicInvokeExtentcan returnObject.
I have added the following rule in the rules/convert/builtin_functions.rules file:
=== modified file 'rules/convert/builtin_functions.rules'
--- old/rules/convert/builtin_functions.rules 2024-09-13 16:48:05 +0000
+++ new/rules/convert/builtin_functions.rules 2025-02-03 07:45:05 +0000
@@ -709,7 +709,10 @@
<rule>ftype == prog.kw_dyn_invk
<rule>upPath(this, prog.assignment, prog.expression)
<action>methodText = "ObjectOps.invokeStandalone"</action>
- <action on="false">methodText = "ObjectOps.invoke"</action>
+ <rule on="false">this.parent.parent.getChildAt(0).isAnnotation("extent")
+ <action>methodText = "ObjectOps.dynamicInvokeExtent"</action>
+ <action on="false">methodText = "ObjectOps.invoke"</action>
+ </rule>
</rule>
<rule>getNoteBoolean("dynamic-poly")
<action>methodText = sprintf("%sPoly", methodText)</action>
Here I checked if the left operand of the assignment has an EXTENT annotation (meaning it holds an extent) and if so. I've made some minimal checks to see if this breaks the already supported functionality and as far as it goes, it doesn't seem to do it.
Now, I consider myself to be no expert in TRPL and I would really appreciate a feedback regarding this modification.
#16 Updated by Constantin Asofiei over 1 year ago
Post the AST on the this.parent.parent level, for an example from note #6490-13, and I'll comment.
#17 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Post the AST on the
this.parent.parentlevel, for an example from note #6490-13, and I'll comment.
Here:
[java] = [ASSIGN]:154618822710 @7:6
[java] res1 [VAR_INT]:154618822713 @7:1
[java] (oldtype=2977, extent=3, refid=154618822692, support_level=16400)
[java] expression [EXPRESSION]:154618822714 @0:0
[java] (support_level=16400)
[java] DYNAMIC-INVOKE [FUNC_POLY]:154618822715 @7:8
[java] (oldtype=626, returnsunknown=true, builtin=true, param_modes=, support_level=16400)
[java] clsRef [VAR_CLASS]:154618822717 @7:23
[java] (oldtype=2977, source-file=./task/HelperClass.cls, is-interface=false, support_level=16400, full-java-class=com.goldencode.testcases.task.HelperClass, is-java=false, is-class=true, simple-java-class=HelperClass, indirect-dotnet=false, qualified=task.helperclass, is-enum=false, dotnet-cls=false, refid=154618822665, builtin-cls=false, containing-package=com.goldencode.testcases.task)
[java] "testFuncUnknown" [STRING]:154618822720 @7:31
[java] (is-literal=true, support_level=16400)
#18 Updated by Constantin Asofiei over 1 year ago
- we are in an assignment
- dynamic-invoke is on the right-side of the assignment
- the left-side of the assignment is extent
- is checked via
upPath(this, prog.assignment, prog.expression) - although I don't think dynamic-invoke can be lvalue, we can use
parent.indexPos == 1to enforce this is on right-side - in
common-progress.rules, there isis_extent_lvalue_refwhich can be used. You will see this also includes checking for extent fields.
Otherwise, please make sure to check what happens if dynamic-invoke is in a complex expression like 1 + dynamic-invoke("...."), and dynamic-invoke returns 'extent'.
#19 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
assignMultiPolyalready hasObject sourceparameter def. So the question is: where do you make the check thatdynamicInvokeExtentshould return an array or not? I think we need to make this check inassignMultiPoly, and in this casedynamicInvokeExtentcan returnObject.
The function already checks for an array type and everything works on that part.
Constantin Asofiei wrote:
So you need to determine a couple of issues:How do we determine this:
- we are in an assignment
- dynamic-invoke is on the right-side of the assignment
- the left-side of the assignment is extent
- is checked via
upPath(this, prog.assignment, prog.expression)- although I don't think dynamic-invoke can be lvalue, we can use
parent.indexPos == 1to enforce this is on right-side- in
common-progress.rules, there isis_extent_lvalue_refwhich can be used. You will see this also includes checking for extent fields.
I made the following change:
=== modified file 'rules/convert/builtin_functions.rules'
--- old/rules/convert/builtin_functions.rules 2024-09-13 16:48:05 +0000
+++ new/rules/convert/builtin_functions.rules 2025-02-04 11:08:42 +0000
@@ -709,7 +709,14 @@
<rule>ftype == prog.kw_dyn_invk
<rule>upPath(this, prog.assignment, prog.expression)
<action>methodText = "ObjectOps.invokeStandalone"</action>
- <action on="false">methodText = "ObjectOps.invoke"</action>
+
+ <rule on="false">evalLib("is_extent_lvalue_ref",
+ this.parent.parent.getChildAt(0),
+ false) and
+ parent.indexPos == 1
+ <action>methodText = "ObjectOps.dynamicInvokeExtent"</action>
+ <action on="false">methodText = "ObjectOps.invoke"</action>
+ </rule>
</rule>
<rule>getNoteBoolean("dynamic-poly")
<action>methodText = sprintf("%sPoly", methodText)</action>
Otherwise, please make sure to check what happens if
dynamic-invokeis in a complex expression like1 + dynamic-invoke("...."), anddynamic-invokereturns 'extent'.
The code is currently failing here because of at least 2 reasons:
1. the methods that are generated when it cannot be inferred from the context that the return type is an extent are still Object.invoke which only return BaseDataType and not an array.
2. the way DynamicOps's methods are declared:public static BaseDataType *operation*(BaseDataType op1, BaseDataType op2) -> They don't accept array types and an error is thrown for incompatible casting.
I thought of another solution that might be better for the ObjectOps class but I don't know how it will impact the other places that these functions are called. Instead of making the functions return a Object type we might create a wrapper like this:
public class Wrapper<T>
{
private T value;
public T getValue()
{
return value;
}
public void setValue(T value)
{
this.value = value;
}
}
And declare the invoke methods of ObjectOps class like this:
public static Wrapper<?> myFunc(Object... args) {...}
Then we could just check if the wrapper.getValue() is a BaseDataType or an array. And this way we don't need specific invoke methods only for the case when it can be inferred when the ones already existing can handle extents.
#20 Updated by Constantin Asofiei over 1 year ago
The builtin_functions changes look good.
For the secondary question: how does the converted Java code look? Because if we expect to use ObjectOps.invoke only and only when it needs to return a BDT and not an array, then we can enforce the proper ERROR condition raised in 4GL, directly in ObjectOps.invoke.
#21 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
For the secondary question: how does the converted Java code look? Because if we expect to use
ObjectOps.invokeonly and only when it needs to return a BDT and not an array, then we can enforce the proper ERROR condition raised in 4GL, directly inObjectOps.invoke.
METHOD PUBLIC INTEGER testFunc ():
RETURN 3.
END METHOD.
METHOD PUBLIC INTEGER EXTENT 3 myFunc1 ():
DEFINE VARIABLE myArr AS INTEGER EXTENT 3 INITIAL [1, 2, 3] NO-UNDO.
RETURN myArr.
END METHOD.
USING task.HelperClass. DEFINE VARIABLE clsRef AS CLASS HelperClass. clsRef = NEW HelperClass(). DEFINE VARIABLE res5 AS INTEGER EXTENT 3 NO-UNDO. //Works res5 = 1 + DYNAMIC-INVOKE(clsRef, "testFunc"). DISPLAY res5 WITH FRAME f5 NO-LABELS. // 4 4 4 DEFINE VARIABLE res6 AS INTEGER NO-UNDO. //Should return the error: Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905) res6 = DYNAMIC-INVOKE(clsRef, "myFunc1"). //returns an EXTENT DISPLAY res6 WITH FRAME f6 NO-LABELS. DEFINE VARIABLE res7 AS INTEGER NO-UNDO. //Should return the error: ** An array was specified in an expression, on the right-hand side of an assignment, or as a parameter when no array is appropriate or expected. (361) res7 = 1 + DYNAMIC-INVOKE(clsRef, "myFunc1"). //returns an EXTENT DISPLAY res7 WITH FRAME f7 NO-LABELS.
Converted code:
package com.goldencode.testcases.task;
import com.goldencode.p2j.util.*;
import com.goldencode.testcases.ui.task.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.testcases.task.HelperClass;
import static com.goldencode.p2j.util.BlockManager.*;
import static com.goldencode.p2j.util.InternalEntry.Type;
import static com.goldencode.p2j.util.MathOps.*;
import static com.goldencode.p2j.util.ArrayAssigner.*;
/**
* Business logic (converted to Java from the 4GL source code
* in task/p.p).
*/
public class P
{
PF6 f6Frame = GenericFrame.createFrame(PF6.class, "f6");
PF5 f5Frame = GenericFrame.createFrame(PF5.class, "f5");
PF7 f7Frame = GenericFrame.createFrame(PF7.class, "f7");
@LegacySignature(type = Type.VARIABLE, name = "clsRef")
object<? extends HelperClass> clsRef = UndoableFactory.object(HelperClass.class);
@LegacySignature(type = Type.VARIABLE, extent = 3, name = "res5")
integer[] res5 = TypeFactory.integerExtent(3);
@LegacySignature(type = Type.VARIABLE, name = "res6")
// 4 4 4
integer res6 = TypeFactory.integer();
@LegacySignature(type = Type.VARIABLE, name = "res7")
integer res7 = TypeFactory.integer();
/**
* External procedure (converted to Java from the 4GL source code
* in task/p.p).
*/
@LegacySignature(type = Type.MAIN, name = "task/p.p")
public void execute()
{
externalProcedure(P.this, new Block((Body) () ->
{
f5Frame.openScope();
f6Frame.openScope();
f7Frame.openScope();
clsRef.assign(ObjectOps.newInstance(HelperClass.class));
// Works
assignMulti(res5, DynamicOps.plus(new integer(1), ObjectOps.invoke(clsRef, "testFunc")));
FrameElement[] elementList0 = new FrameElement[]
{
new Element(subscript(res5, 1), f5Frame.widgetRes5Array0()),
new Element(subscript(res5, 2), f5Frame.widgetRes5Array1()),
new Element(subscript(res5, 3), f5Frame.widgetRes5Array2())
};
f5Frame.display(elementList0);
// Should return the error: Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905)
res6.assign(ObjectOps.invoke(clsRef, "myFunc1", "I", 2));
FrameElement[] elementList1 = new FrameElement[]
{
new Element(res6, f6Frame.widgetRes6())
};
f6Frame.display(elementList1);
// Should return the error: ** An array was specified in an expression, on the right-hand side of an assignment, or as a parameter when no array is appropriate or expected. (361)
res7.assign(DynamicOps.plus(new integer(1), ObjectOps.invoke(clsRef, "myFunc1", "I", 2)));
FrameElement[] elementList2 = new FrameElement[]
{
new Element(res7, f7Frame.widgetRes7())
};
f7Frame.display(elementList2);
}));
}
}
Sorry, I made some changes because the methdos were wrong
#22 Updated by Constantin Asofiei over 1 year ago
Why should this raise an ERROR? Neither testFunc2 or res6 are extent:
DEFINE VARIABLE res6 AS INTEGER NO-UNDO. //Should return the error: Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905) res6 = DYNAMIC-INVOKE(clsRef, "testFunc2", 2).
#23 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Why should this raise an ERROR? Neither testFunc2 or res6 are extent:
[...]
Sorry, you're right. I edited the message later when I realised.
#24 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Because if we expect to use
ObjectOps.invokeonly and only when it needs to return a BDT and not an array, then we can enforce the proper ERROR condition raised in 4GL, directly inObjectOps.invoke.
I don't see how we can make ObjectOps.invoke to always return a BDT while also supporting returning extents without some sort of costly reflection instructions that will look at the methods of the legacy class before making a call, or one of the solutions we discussed earlier.
What do you think the solution should be?
#25 Updated by Paul Bodale over 1 year ago
I've wrote the implementation for the generic wrapper of return type of ObjectOps.invoke solution and made a commit on branch #6490a, revision 15669.
In this implementation I've tried to make use of the trick of using a generic wrap return type like this:
public class DynInvkResultWrapper<T>
{
private T data;
public DynInvkResultWrapper(T data)
{
this.data = data;
}
public T getData()
{
return data;
}
public void setData(T data)
{
this.data = data;
}
}
I have tested this solution only on INTEGER EXTENT just to provide a template although I think the code is pretty much the same except for some edge cases. Nonetheless it still needs to be tested on every data type to make sure it works 100% correct should we choose to go with this.I also have to mention that didn't include any documentation because it would be quite a lot and I'm still waiting for a confirmation on this approach.
In the meantime I will followup with the second, more simpler proposal to just use ObjectOps.invoke for every DYNAMIC-INVOKE and make these methods return an Object.
#26 Updated by Constantin Asofiei over 1 year ago
Paul, no, using DynInvkResultWrapper I don't think it will work. DYNAMIC-INVOKE can be used a part of any expression or argument. Adding this wrapper it will be like adding a new data type.
And we can't make ObjectOps.invoke return Object, either.
#27 Updated by Alexandru Lungu over 1 year ago
Current approach¶
I agree with Constantin thatObjectOps.invoke should stick on returning BDT. The conversion should do its absolute best to either generate .invoke for BDT or dynamicInvokeExtent for BDT[]. This is because there is a lot of run-time that won't compile if .invoke would generate Object (for instance, DynamicOps can't have any parameter Object, and if it does, it will fail at compile time!!). The only outcome with such approach is:
- run-time refactoring, adding overloads with Object everywhere - too tricky, too much work. Especially if
dynamic-invokeis used as parameter for another method that statically evaluates types. If we intend to statically call the method "hello(character)", but generate "hello(Object)" because the parameter is a dynamic-invoke, this will simply not compile - mission failed. The same goes withDynInvkResultWrapper.
dynamic-invoke can occur:
- assignments (r value and l value)
- expressions: arithmetic (+, -, *, /), conditional (==, !=, if (...)), relational (<, <=, >, >=)
- control-flow: parameter to a run procedure, function call, method invoke (and their dynamic counter-parts).
- built-in function parameters (truncate(dyanmic-invoke(...))
- buffer interactions: user.name = dynamic-invoke(...)
- persistence interactions: for each user where user.name = dynamic-invoke(...)
- statements:
- return dynamic-invoke()
- case-when statements
- if statements
- repeat
- OO cases (property assignment, others)
- OTHERS
For all such cases, we should do our best to infer if .invoke or .dynamicInvokeExtent is needed. Currently, there is work only for assignment if the lvalue is trivial and is extent.
For cases where we can't do that, I agree with Constantin that we need special static functions to take care of that, like assignMulti.
Greg/Constantin: I would personally prefer .invoke, .extentInvoke and .dynamicInvoke as three different API. The first is a BDT inferred invoker, the second is a BDT extent inferred invoker and the third is an Object general invoker that should be generated in very specific poly cases, if any. I would recommend to try our best to infer the need of a either .invoke or .invokeExtent and let dynamicInvoke for very specific cases if any.
For dynamic-property(lc, "somePropWhichCanBeExtent") = dynamic-invoke(lc, "someMethodWhichMayReturnExtent")., the dynamic-invoke shall indeed return Object and let setDynamicProperty do its job.
#28 Updated by Constantin Asofiei over 1 year ago
Paul, please make sure that your tests (and runtime support) cover this case:
DYNAMIC-PROPERTY(oObject, propName) = DYNAMIC-INVOKE(oJsonObject,cMethod,cPropName) NO-ERROR.
Either side can be extent or not, so you have 4 cases (scalar/scalar, scalar/extent, extent/scalar, extent/extent) for the dynamic-property and dynamic-invoke.
#29 Updated by Paul Bodale over 1 year ago
While running the tests as me and Constantin discussed at the office, I found a specific case where the function is_extent_lvalue_ref from TRPL was returning the wrong results. When checking for a non-subscripted reference, the function disregarded the case when the second parameter (which states if we want to return a match on such subscript references) is false.
This is the old function:
<function name="is_extent_lvalue_ref">
<parameter name="target" type="com.goldencode.ast.Aast" />
<parameter name="noSub" type="java.lang.Boolean" />
<variable name="ref" type="com.goldencode.ast.Aast" />
<variable name="ttype" type="java.lang.Integer" />
<variable name="extent" type="java.lang.Long" />
<return name="match" type="java.lang.Boolean" />
<rule>ttype = target.type</rule>
<rule>match = false</rule>
<rule>
(ttype > prog.begin_vartypes and ttype < prog.end_vartypes) or
(ttype > prog.begin_fieldtypes and ttype < prog.end_fieldtypes)
<rule>!noSub and target.descendant(prog.lbracket, 1)
<rule>match = true</rule>
</rule>
<rule>!match
<action>ref = execLib("dereference", target)</action>
<rule>ref != null and ref.isAnnotation("extent")
<action>extent = ref.getAnnotation("extent")</action>
<rule>extent > 0 or extent == -1
<rule>match = true</rule>
</rule>
</rule>
</rule>
</rule>
</function>
This is the revised function:
<function name="is_extent_lvalue_ref">
<parameter name="target" type="com.goldencode.ast.Aast" />
<parameter name="noSub" type="java.lang.Boolean" />
<variable name="ref" type="com.goldencode.ast.Aast" />
<variable name="ttype" type="java.lang.Integer" />
<variable name="extent" type="java.lang.Long" />
<return name="match" type="java.lang.Boolean" />
<rule>ttype = target.type</rule>
<rule>match = false</rule>
<rule>
(ttype > prog.begin_vartypes and ttype < prog.end_vartypes) or
(ttype > prog.begin_fieldtypes and ttype < prog.end_fieldtypes)
<action>ref = execLib("dereference", target)</action>
<rule>ref != null and ref.isAnnotation("extent")
<action>extent = ref.getAnnotation("extent")</action>
<rule>extent > 0 or extent == -1
<rule>match = true</rule>
</rule>
</rule>
<rule>match and noSub
<rule>target.descendant(1, prog.lbracket)
<action>match = false</action>
</rule>
</rule>
</rule>
</function>
Basically the function first checked if there was a left square bracket right next to the reference, assumed that it was from an extent subscript and if the parameter noSub was set to false then the function would return true. If instead the parameter was true, it further looked to the reference given and it found that it was indeed a reference to an extent and it would also return true which is wrong.
In my implementation I'm firstly looking to the reference to see if it represents an extent and if so, I look to see if it has a left square bracket to return false if the parameter noSub is set to false and return false if it's the case.
I'm not sure if the function was wrote like that on purpose. I only found one other place where it's used: rules/convert/expressions.rules on line 1506.
#30 Updated by Paul Bodale over 1 year ago
I discovered the following conversion problem:
While trying to convert the following program
FUNCTION funcExpExt3 RETURNS INTEGER (v AS INTEGER EXTENT 3): END FUNCTION. funcExpExt3(DYNAMIC-INVOKE(clsRef, "retVar")).
The call would convert to:
@LegacySignature(type = Type.FUNCTION, name = "funcExpExt3", returns = "INTEGER", parameters =
{
@LegacyParameter(name = "v", type = "INTEGER", extent = 3, mode = "INPUT")
})
public integer funcExpExt3(final integer[] _v)
{
integer[] v = TypeFactory.initInput(_v);
return function(this, "funcExpExt3", integer.class, new Block());
}
funcExpExt3(new integer(new integer(ObjectOps.invokeExtent(clsRef, "retVar"))));
I managed to get rid of the double constructor problem by modifying the rules/convert/base_structure.xml:
=== modified file 'rules/convert/base_structure.xml'
--- old/rules/convert/base_structure.xml 2023-09-21 20:06:52 +0000
+++ new/rules/convert/base_structure.xml 2025-02-20 14:55:06 +0000
@@ -729,7 +729,9 @@
</while>
<rule>nodeRef != null
- <action>nodeRef.putAnnotation("chp_wrapper", wrapper)</action>
+ <rule>getNoteLong("oldtype") != prog.kw_dyn_invk
+ <action>nodeRef.putAnnotation("chp_wrapper", wrapper)</action>
+ </rule>
</rule>
<action>pmode = modes.substring(parIdx, parIdx + 1)</action>
@@ -764,9 +766,11 @@
assignment-compatible can't be used here: the cast may have a common
super-class with the defined argument (like TextOps for longchar and
character), but the caller may not be a sub-class -->
- <rule>casttype == null or !casttype.equals(wrapper))
- <action>putNote("wrap_parameter", true)</action>
- <action>putNote("classname", wrapper)</action>
+ <rule>casttype == null or !casttype.equals(wrapper)
+ <rule>getNoteLong("oldtype") != prog.kw_dyn_invk
+ <action>putNote("wrap_parameter", true)</action>
+ <action>putNote("classname", wrapper)</action>
+ </rule>
</rule>
</rule>
</rule>
The code still won't compile as the function is expecting an integer[] and the ObjectOps.invokeExtent is returning a BaseDataType[]. The same problem appears every time a DYNAMIC-INVOKE is given as a parameter (including the versions returning BDT and Object) to a function or a method, as this is one of the cases where we are able to infer what type is expected from the DYNAMIC-INVOKE call.
- ** Invalid character in numeric input <character>. (76)
- Incompatible datatypes found during runtime conversion. (5729)
- [Simple variable case]Routine <procedure\path> sent called routine <functionName+path\to\procedure> mismatched parameters. (2570) | [EXTENT case]Could not convert data for array assignment. (14907)
How should I tackle this problem?
#31 Updated by Constantin Asofiei over 1 year ago
Paul, about the 'extent' parameter used in a call like extentMethod(dynamic-invoke()). There is another possibility: OE does not check at runtime the extent size to match between argument and parameter, just that is extent. This may make more sense than 'runtime target resolution in OE keeps a separate list of overloads which failed on extent match but match on everything else'.
#32 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Paul, about the 'extent' parameter used in a call like
extentMethod(dynamic-invoke()). There is another possibility: OE does not check at runtime the extent size to match between argument and parameter, just that is extent. This may make more sense than 'runtime target resolution in OE keeps a separate list of overloads which failed on extent match but match on everything else'.
I'll look into that.
In the meantime, I've found this problem in the converted code:
//Methods:
METHOD PUBLIC CHARACTER retSpecifiedMethod (mthd AS CHARACTER):
RETURN mthd.
END METHOD.
METHOD PUBLIC INTEGER EXTENT 3 nonStaticNoArgsRetIntExt3 ():
DEFINE VARIABLE myArr AS INTEGER EXTENT 3 INITIAL [1, 2, 3] NO-UNDO.
RETURN myArr.
END METHOD.
//Code:
DEFINE VARIABLE res AS INTEGER EXTENT 3 NO-UNDO.
res = DYNAMIC-INVOKE(clsRef, DYNAMIC-INVOKE(clsRef, "retSpecifiedMethod", "nonStaticNoArgsRetIntExt3")).
DISPLAY res WITH NO-LABELS. // 1 2 3
Converted code:
assignMultiPoly(res, ObjectOps.dynamicInvoke(clsRef, new character(ObjectOps.dynamicInvoke(clsRef, "retSpecifiedMethod", "I", "nonStaticNoArgsRetIntExt3"))));
The problem here is that there is no constructor for character class that takes an Object.
I assume that we can take out the character constructor with TRPL and make the ObjectOps methods be capable of receiving Object as arguments, or we can make a constructor for the character class receiving a Object. This would be really easy to do as we only need to check if it is an instance of a BaseDataType and delegate that already existing constructor.
As per official documentation, DYNAMIC-INVOKE is only supposed to be receiving character expressions for the second parameter and if it is a static call then also for the first parameter. I think that the second option it will provide a bit more clarity.
What should I do?
#33 Updated by Constantin Asofiei over 1 year ago
So you are using DYNAMIC-INVOKE on the 2nd argument of the DYNAMIC-INVOKE, which must be a character.
If you use POLY at a builtin function call's argument, that's what SignatureHelper is about - it specifies the number and type of arguments for handle methods and builtin functions.
In this case, add it to the 'backlog', it may be that a character(Object) constructor is enough. Please focus on the extent issues and argument passing for actual func/method calls.
#34 Updated by Paul Bodale over 1 year ago
Constantin, regarding our discussion from yesterday about all the different call types... I haven't found any list like that here on redmine but I found this short list in the progress documentation: https://docs.progress.com/bundle/abl-reference/page/CALL-TYPE-attribute.html
Other than what Alexandru listed above and the DYNAMIC-PROPERTY case, there are the following cases:- user defined functions that we already covered
- all the handle/widget methods and attributes
I've spoken to Alexandru earlier about how should I approach the testing/implementation of the builtin functions because there are so many of them and he suggested to pick and resolve a few of the ones implemented in the same *Ops.java class as they are organized this way because they are closely related.
Alexandru Lungu wrote:
Bottom-line is that we should comprehensively test the places wheredynamic-invokecan occur:
- assignments (r value and l value)
- expressions: arithmetic (+, -, *, /), conditional (==, !=, if (...)), relational (<, <=, >, >=)
- control-flow: parameter to a run procedure, function call, method invoke (and their dynamic counter-parts).
- built-in function parameters (truncate(dyanmic-invoke(...))
- buffer interactions: user.name = dynamic-invoke(...)
- persistence interactions: for each user where user.name = dynamic-invoke(...)
- statements:
- return dynamic-invoke()
- case-when statements
- if statements
- repeat
- OO cases (property assignment, others)
- OTHERS
dynamic-property(lc, "somePropWhichCanBeExtent") = dynamic-invoke(lc, "someMethodWhichMayReturnExtent").
#35 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Constantin, regarding our discussion from yesterday about all the different call types... I haven't found any list like that here on redmine but I found this short list in the progress documentation: https://docs.progress.com/bundle/abl-reference/page/CALL-TYPE-attribute.html
Other than what Alexandru listed above and the DYNAMIC-PROPERTY case, there are the following cases:
- user defined functions that we already covered
There are only a few places where you can have the 'may be extent or BDT', for DYNAMIC-PROPERTY, DYNAMIC-FUNCTION, DYNAMIC-INVOKE, NEW, DYNAMIC-NEW, CALL:SET-PARAMETER, ParameterList:setParameter, RUN, property getter and setters, field assignment (extent or scalar field). Only where this can be passed as an argument is of concern. There are also the virtual functions/procedures, like FUNCTION func0 RETURNS int IN <handle>, which emit via ControlFlowOps. There are also the direct function calls and direct OO method calls, which emit as Java method calls.
- all the handle/widget methods and attributes
All the cases which are not expected to be an actual argument for a call will (or otherwise in 'maybe' or 'this must be extent') always emit ObjectOps.invoke, which returns BDT. Cover a few of them to make sure it works. Do not cover i.e. DYNAMIC-INVOKE(ref, DYNAMIC-INVOKE), as the second parameter is mandatory to be a character.
Bottom-line is that we should comprehensively test the places wheredynamic-invokecan occur:
- assignments (r value and l value)
R-VALUE is either BDT if inside an expression, or 'maybe' if this is standalone. DYNAMIC-INVOKE can't appear as L-VALUE, but DYNAMIC-PROPERTY can.
- expressions: arithmetic (+, -, *, /), conditional (==, !=, if (...)), relational (<, <=, >, >=)
This is always BDT, so ObjectOps.invoke.
- control-flow: parameter to a run procedure, function call, method invoke (and their dynamic counter-parts).
This is 'maybe'.
- built-in function parameters (truncate(dyanmic-invoke(...))
This is always BDT.
- buffer interactions: user.name = dynamic-invoke(...)
This depends if the field is extent or not. If is extent, then is 'maybe'. Otherwise, is always BDT.
- persistence interactions: for each user where user.name = dynamic-invoke(...)
Good point.
- statements:
- return dynamic-invoke()
Yes, do test this, combinations of both dynamic extent for the function/method, and for dynamic-invoke.
- case-when statements
This is always either numeric or string. Always BDT.
- if statements
This is always logical.
- repeat
This is always logical.
#36 Updated by Paul Bodale over 1 year ago
Found a problem with the constructor resolution of a call with DYNAMIC-INVOKE given as argument.
CLASS simple.Person:
DEFINE PROPERTY pName AS INTEGER NO-UNDO
PUBLIC GET.
PRIVATE SET.
DEFINE PROPERTY friends AS INTEGER EXTENT 3 NO-UNDO
PUBLIC GET.
PRIVATE SET.
METHOD PUBLIC INTEGER EXTENT 1 retExt1 ():
DEFINE VARIABLE ext AS INTEGER EXTENT 1 INITIAL [1] NO-UNDO.
RETURN ext.
END METHOD.
CONSTRUCTOR PUBLIC Person ():
END CONSTRUCTOR.
CONSTRUCTOR PUBLIC Person (n AS INTEGER):
pName = n.
END CONSTRUCTOR.
CONSTRUCTOR PUBLIC Person (f AS INTEGER EXTENT 3):
friends = f.
END CONSTRUCTOR.
END CLASS.
//Code:
DEFINE VARIABLE clsRef AS CLASS simple.Person.
clsRef = NEW simple.Person(). // works
//Ambiguous method call. Could not resolve Person reference. (13844)
clsRef = NEW simple.Person(DYNAMIC-INVOKE(clsRef, "retExt1")).
This converts to:
clsRef.assign(ObjectOps.newInstance(com.goldencode.testcases.simple.Person.class)); // works // Should throw: Ambiguous method call. Could not resolve Person reference. (13844) clsRef.assign(ObjectOps.newDynamicInstance(com.goldencode.testcases.simple.Person.class, "I", ObjectOps.dynamicInvoke(clsRef, "retExt1")));
After I traced the call I noticed that for trying to create a object by calling a non-existent constructor, FWD simply creates an empty <T>object reference and doesn't throw any errors. There are quite a few methods being called some returning boolean false, some returning null InternalEntry's.
Where would be a good place to throw the error?
#37 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
After I traced the call I noticed that for trying to create a object by calling a non-existent constructor, FWD simply creates an empty
<T>objectreference and doesn't throw any errors. There are quite a few methods being called some returning booleanfalse, some returningnullInternalEntry's.
Do you refer to initializeLegacyObject, or something else?
#38 Updated by Paul Bodale over 1 year ago
ObjectOps.newDynamicInstance- 3 overloaded versions
ObjectOps.newInstanceInternal ControlFlowOps.initializeLegacyObjectwhich returnsbooleanControlFlowOps.resolveLegacyEntrywhich returnsInternalEntry
Which one should throw the error?
#39 Updated by Constantin Asofiei over 1 year ago
The other error (if it can't find a constructor with that signature), does it work properly? It should be in the similar mechanism, for what it is done when invoking OO methods (so I think resolveLegacyEntry).
#40 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
The other error (if it can't find a constructor with that signature), does it work properly?
I haven't found other error like that, only 13844.
If there is no constructor with the signature given by the calling arguments, the code simply won't compile.
#41 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Constantin Asofiei wrote:
The other error (if it can't find a constructor with that signature), does it work properly?
I haven't found other error like that, only
13844.
If there is no constructor with the signature given by the calling arguments, the code simply won't compile.
Please test with DYNAMIC-NEW, also.
#42 Updated by Paul Bodale over 1 year ago
Same structure of the tests, same problem. There is no error being thrown for a call to a nonexistent constructor.
#43 Updated by Paul Bodale over 1 year ago
This problem might be related to issue #4374
#44 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Same structure of the tests, same problem. There is no error being thrown for a call to a nonexistent constructor.
Please fix it now.
#45 Updated by Paul Bodale over 1 year ago
- File emptyError.png added
I don't think that this is so important but I'll just log it here.
AVM throws an "empty error" for the following case:
PROCEDURE pExpVar:
DEFINE INPUT PARAMETER p1 AS INTEGER NO-UNDO.
Assert:Equals(1, p1).
END PROCEDURE.
DEFINE VARIABLE callH AS HANDLE NO-UNDO.
CREATE CALL callH.
callH:CALL-NAME = "pExpVar".
callH:NUM-PARAMETERS = 1.
callH:SET-PARAMETER(1, "INTEGER", "INPUT", DYNAMIC-INVOKE(clsRef, "retUnknown")).
callH:INVOKE().

Not sure how to test this in FWD.
#46 Updated by Constantin Asofiei over 1 year ago
Check with NO-ERROR the place where is thrown, for ERROR-STATUS:ERROR, ERROR-STATUS:GET-MESSAGE, ERROR-STATUS:GET-NUMBER. And after that, use an ERROR condition without message.
#47 Updated by Paul Bodale over 1 year ago
- File past.xml
added - File base_structure.xml
added
I had the same problem a while back
clsRef:prp = DYNAMIC-INVOKE(clsRef, "retVar").
clsRef.ref().setPrp(new integer(new integer(ObjectOps.dynamicInvoke(clsRef, "retVar"))));
Then I've modified the base_structure.xml in 2 places to include the following rule so that it won't insert the annotation that generates the constructor:
!(evalLib("type_pair", this, prog.func_poly, prog.kw_dyn_func) or
evalLib("type_pair", this, prog.func_poly, prog.kw_dyn_invk) or
evalLib("type_pair", this, prog.func_poly, prog.kw_dyn_prop) or
evalLib("type_pair", this, prog.attr_poly, prog.kw_buf_val) or
this.type == prog.db_ref_non_static)
For this case it seems to not be working.
#48 Updated by Constantin Asofiei over 1 year ago
If prp is extent, does this work if DYNAMIC-INVOKE returns scalar or extent, in 4GL? Because to me it looks like this should have been converted as a pseudo-DYNAMIC-PROPERTY call on the LVALUE.
Keep in mind that LVALUE can be o1:o2:o3:prp = dynamic-invoke.
#49 Updated by Paul Bodale over 1 year ago
- File jast.xml
added
Constantin Asofiei wrote:
If
prpis extent, does this work if DYNAMIC-INVOKE returns scalar or extent, in 4GL?
Yes, as far as I looked, the behavior is the same as assigning to a simple variable. So if for example we have
clsRef:ext3 = DYNAMIC-INVOKE(clsRef, "functionReturningASimpleVariable").
Where ext3 is a property of EXTENT 3, then all the elements would be filled with the value of the returned variable.
#50 Updated by Constantin Asofiei over 1 year ago
And in this case emits as a pseudo-dynamic-property call or a bulk Java setter call?
#51 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
And in this case emits as a pseudo-dynamic-property call or a bulk Java setter call?
It looks like it's choosing setAll<extent_property_name> as the default.
clsRef:ext3 = DYNAMIC-INVOKE(clsRef, "retVar"). clsRef:ext3 = DYNAMIC-INVOKE(clsRef, "retExt3").
clsRef.ref().setAllExt3(new integer(new integer(ObjectOps.invoke(clsRef, "retVar")))); clsRef.ref().setAllExt3(new integer(new integer(ObjectOps.invoke(clsRef, "retExt3"))));
@LegacySignature(type = Type.SETTER, name = "ext3", parameters =
{
@LegacyParameter(name = "var", type = "INTEGER", mode = "INPUT")
})
public void setAllExt3(final integer _var)
{
integer var = TypeFactory.initInput(_var);
internalProcedure(Dihelper.class, this, "ext3", new Block((Body) () ->
{
assignMulti(ext3, var);
}));
}
#52 Updated by Paul Bodale over 1 year ago
Found another conversion problem.
DYNAMIC-PROPERTY(clsRef, "ext3") = DYNAMIC-INVOKE(clsRef, "retExt3").
ASSIGN
DYNAMIC-PROPERTY(clsRef, "ext3") = DYNAMIC-INVOKE(clsRef, "retExt3").
Converts to:
ObjectOps.setDynamicProperty(clsRef, new character("ext3"), ObjectOps.dynamicInvoke(clsRef, "retExt3"));
ObjectOps.setDynamicProperty(clsRef, "ext3", ObjectOps.dynamicInvoke(clsRef, "retExt3"));
Second line won't compile because there is no setDynamicProperty method that takes in a String for the second argument.
#53 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Second line won't compile because there is no
setDynamicPropertymethod that takes in aStringfor the second argument.
You can add the overload.
#54 Updated by Paul Bodale over 1 year ago
I wrote some more tests for setting a property and implemented the changes we previously discussed about making the invocation a "artificial" DYNAMIC-PROPERTY call.
While testing I found this example:
DYNAMIC-PROPERTY(dInst:cInst:bInst:aInst, "ext3") = DYNAMIC-INVOKE(clsRef, "retVar"). dInst:cInst:bInst:aInst:ext3 = DYNAMIC-INVOKE(clsRef, "retVar").
Which converts to:
dInst.ref().getCInst().ref().getBInst().ref().getAInst(((object) ObjectOps.setDynamicProperty(dInst.ref().getCInst().ref().getBInst().ref().getAInst(), new character("ext3"), ObjectOps.dynamicInvoke(clsRef, "retVar"))));
dInst.ref().getCInst().ref().getBInst().ref().getAInst(((object) ObjectOps.setDynamicProperty(dInst.ref().getCInst().ref().getBInst().ref().getAInst(), new character("ext3"), ObjectOps.dynamicInvoke(clsRef, "retVar"))));
First line should convert to a normal DYNAMIC-PROPERTY call which I used as a model for my changes. They both convert wrong for this example and a casting to
object is added on top of that. It seems to be a problem in the conversion of DYNAMIC-PROPERTY.
Besides this issue, I have another question. Should we make the invocation an artificial DYNAMIC-PROPERTY only when we encounter an extent property AND rvalue is a DYNAMIC-INVOKE? Or just when we've got a DYNAMIC-INVOKE on the right side of the assignment?
#55 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
First line should convert to a normal DYNAMIC-PROPERTY call which I used as a model for my changes. They both convert wrong for this example and a casting to
objectis added on top of that. It seems to be a problem in the conversion of DYNAMIC-PROPERTY.
Did you test with trunk?
Besides this issue, I have another question. Should we make the invocation an artificial DYNAMIC-PROPERTY only when we encounter an extent property AND rvalue is a DYNAMIC-INVOKE? Or just when we've got a DYNAMIC-INVOKE on the right side of the assignment?
When we discussed, you showed me an example where LVALUE is scalar property and RVALUE was DYNAMIC-INVOKE returning an extent - in this case, we need to emit again DYNAMIC-PROPERTY pseudo-invocation.
PS: please use a function in common-progress.rules for all cases where you check for DYNAMIC-INVOKE cases - because we will need to expand this to any POLY which can return scalar or extent, like DYNAMIC-FUNCTION.
#56 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Did you test with trunk?
Yes, the result is similar.
PS: please use a function in
common-progress.rulesfor all cases where you check forDYNAMIC-INVOKEcases - because we will need to expand this to any POLY which can return scalar or extent, likeDYNAMIC-FUNCTION.
You mean a function that returns the type of DYNAMIC-INVOKE api for a specific case or maybe something more like the rule that we added in the base_structure.xml to avoid double constructor wrapping?
<rule>!(evalLib("type_pair", this, prog.func_poly, prog.kw_dyn_func) or
evalLib("type_pair", this, prog.func_poly, prog.kw_dyn_invk) or
evalLib("type_pair", this, prog.func_poly, prog.kw_dyn_prop) or
evalLib("type_pair", this, prog.attr_poly, prog.kw_buf_val) or
this.type == prog.db_ref_non_static)
#57 Updated by Constantin Asofiei over 1 year ago
- in
assignment_style_stmt_rewriting, line 207 needs to ignore if this isdynamic-property:
<rule>isProp and type != prog.kw_dyn_curv and type != prog.kw_dyn_prop
- for the
RETURN DYNAMIC-INVOKE- please make tests, and we'll discuss if we need to implement this in this phase or not.
#58 Updated by Paul Bodale over 1 year ago
- for the
RETURN DYNAMIC-INVOKE- please make tests, and we'll discuss if we need to implement this in this phase or not.
About this, I've noticed a weird behavior in progress:
Given the following function declared as returning INTEGER:
FUNCTION funcRetInt RETURNS INTEGER (INPUT func AS CHARACTER):
RETURN DYNAMIC-INVOKE(clsRef, func).
END FUNCTION.
Calling this function with the following setting will NOT fail immediately:
DEFINE VARIABLE res AS INTEGER NO-UNDO.
res = funcRetInt("retExt3") NO-ERROR.
MESSAGE error-status:error. // no
MESSAGE error-status:num-messages. // 0
MESSAGE AssertExt:getLastError(). // <empty>
But if we do anything with that variable after the initial call then an error would be thrown but the error-status:error would still be negative:
DEFINE VARIABLE res AS INTEGER NO-UNDO.
res = funcRetInt("retExt3").
res = res NO-ERROR.
MESSAGE error-status:error. // no
MESSAGE error-status:num-messages. // 1
MESSAGE AssertExt:getLastError(). // Incompatible datatypes found during runtime conversion. (5729)
Also, if instead we call funcRetInt with a string representing a void method, an error would be displayed on the screen but error-status:error is negative and error-status:num-messages is 0.
The documentation states that a user defined function can't raise an error to the caller. Instead they recommend to just reference the result of a call in a expression .
#59 Updated by Paul Bodale over 1 year ago
Status as of March 19¶
- All tests are passing conversion and compilation phases
- Here are the known problems:
- Arithmetic ops:
- In progress the expression
1 / DYNAMIC-INVOKE(clsRef, "returnExtent")is valid and returns?when assigned to a variable and an array of?when assigned to an extent. FWD doesn't currently support this. var1 / var2 / DYNAMIC-INVOKE(clsRef, "voidMethod")returns?BUTvar1 / DYNAMIC-INVOKE(clsRef, "voidMethod")andDYNAMIC-INVOKE(clsRef, "voidMethod") / var1throw error. FWD throws error in the first case but behaves correctly on the other 2.- Wrong errors are thrown for some cases of -, + and * operations.
- In progress the expression
- Block statements:
DO WHILE DYNAMIC-INVOKE(clsRef, "retVarChar")doesn't throw error but it should.REPEAT WHILE DYNAMIC-INVOKE(clsRef, "retVarChar")doesn't throw error but it should.
- Call types:
- (object method)
SetParameter(1, "INTEGER", "INPUT", DYNAMIC-INVOKE(clsRef, "returnExtent3"))(object method) doesn't throw error but it should. - (object method)
params:SetParameter(1, "INTEGER EXTENT", "INPUT", DYNAMIC-INVOKE(clsRef, "retExt1")). genClass:Invoke(clsRef, "voidMthdExpExt3", params).
Match on wrong method results in no error being thrown. - calling internal procedures in some cases results in the wrong errors.
- calling methods in some cases results in the wrong errors.
- (object method)
- Widgets/Handles:
- (this is non-related) Dynamic combo-box doesn't have the same behavior.
- Logical ops:
DYNAMIC-INVOKE(clsRef, "returnExtent") AND logicalVaris a valid logical expression and it seems like the call is considered to beFALSE. FWD fails.- Same for the
ORoperator. IF DYNAMIC-INVOKE(clsRef, "returnExtent") THENis a valid statement and it evaluates toFALSE. FWD fails.IF NOT DYNAMIC-INVOKE(clsRef, "returnExtent") THENis a valid statement and it evaluates toTRUE. FWD fails.
- Return statement:
- The problem with the weird behavior described above where the code won't fail immediately.
- Wrong behavior of functions in some cases.
- Wrong behavior of methods in some cases.
DYNAMIC-FUNCTIONreturning extent is not implemented.
- Dynamic property:
chained:call:to:extent3Property = DYNAMIC-INVOKE(clsRef, "returnCharExtent3")not throwing errorStatic:extent3Property = = DYNAMIC-INVOKE(clsRef, "returnCharExtent3")not throwing error
As we discussed, my focus is currently on the SetParameter object method and functions/methods returning extent behavior. After that is DYNAMIC-PROPERTY.
#60 Updated by Paul Bodale over 1 year ago
- Call types:
- (object method) [...] Match on wrong method results in no error being thrown.
There is a problem with this line:params:SetParameter(1, "INTEGER EXTENT", "INPUT", DYNAMIC-INVOKE(clsRef, "retExt1")).
FWD prints the error ** Invalid character in numeric input [. (76).
After I traced the calls I came to this function in the CallParameter class:private Object convertValue(String dataType, boolean isClass, CallMode mode, Object value)
and it fails on this line:val.assign((BaseDataType) ctor.newInstance(value.toString()));
The value is an integer array and the constructor is for the String class which I think is a fallback in case when no appropriate constructor is found. I tried to look into the Constructor class to see exactly why the exact error is being thrown but my IDE goes on the wrong lines and says Source code doesn't match byte-code. I think this is because of the IDE project is set to use a different SDK.
Not sure how to fix this as I might affect the fallback functionality
#61 Updated by Constantin Asofiei over 1 year ago
Well, if value is an integer array, then it seems convertValue is missing support for arrays; what is val at the time newInstance is called?
#62 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
what is
valat the timenewInstanceis called?
BaseDataType val = BaseDataType.generateUnknown(cls); where cls is integer. It remains the unknown value until the line with the error.
Well, if
valueis an integer array, then it seemsconvertValueis missing support for arrays;
Ok so, suppose I need to implement this, the convertValue function should make a copy of the extent variable and return it, right?
#63 Updated by Paul Bodale over 1 year ago
- Return statement:
- Wrong behavior of functions in some cases.
This is a bit messy...
Normally, the RETURN ERROR doesn't throw error but simply returns the ? value and does not raise ERROR to the caller. This means that we cannot simply verify what happened in the function after the call. We can however declare a catch block to handle any errors that might have been thrown in the function body. The RETURN ERROR rule still applies here.
What I discovered is that some errors are actually thrown to the caller but others not, yet the result of such calls is not always the unknown value as it should be in case of an error, but sometimes the result not assigned and the variable which should hold the result stays the same.
There is also the case when trying to return the unknown value via a DYNAMIC-INVOKE call in a function declared as returning extents, the result is that an error is raised in the main procedure and if assigning the result to an extent variable it stays the same.
What FWD is doing now is probably what is intuitively the right behavior. In case of error it executes the statements in the catch block but that is not always the case.
#64 Updated by Paul Bodale over 1 year ago
Paul Bodale wrote:
- Call types:
- (object method)
SetParameter(1, "INTEGER", "INPUT", DYNAMIC-INVOKE(clsRef, "returnExtent3"))(object method) doesn't throw error but it should.
After fixing this problem I again ran all the tests and found out that the behavior of the object method SetParameter is different from the one of handle method. In the exact same case as above, the object method throws the error: 15308 SetParameter requires that the EXTENT keyword must FOLLOW the datatype when the value is an array, but not when the value is a scalar yet the handle method doesn't raise error at the time of the call but at the invocation.
They both come down to the same method in the Call.java class: public static CallParameter createParameter(String, CallMode, Object, boolean, boolean).
#65 Updated by Paul Bodale over 1 year ago
Regarding the problem found while converting the project.
After modifying the builtin_functions.rules by adding the following rule:
<rule>!this.isAnnotation("dynamic-poly") or
!getNoteBoolean("dynamic-poly")
<!-- Possible cases for inferring the return type -->
[....]
The testcase to mimic the issue:
DEFINE VARIABLE h1 AS HANDLE NO-UNDO.
DEFINE VARIABLE h2 AS HANDLE NO-UNDO.
DEFINE VARIABLE h3 AS HANDLE NO-UNDO.
DEFINE TEMP-TABLE tmpTbl1
FIELD nestedTmpTbl1 AS HANDLE.
DEFINE TEMP-TABLE tmpTbl2
FIELD nestedTmpTbl2 AS HANDLE.
DEFINE TEMP-TABLE tmpTbl3
FIELD num AS INTEGER.
CREATE tmpTbl3.
tmpTbl3.num = 3.
h3 = BUFFER tmpTbl3:HANDLE.
CREATE tmpTbl2.
tmpTbl2.nestedTmpTbl2 = h3.
h2 = BUFFER tmpTbl2:HANDLE.
CREATE tmpTbl1.
tmpTbl1.nestedTmpTbl1 = h2.
h1 = BUFFER tmpTbl1:HANDLE.
@Test.
PROCEDURE test1:
DEFINE VARIABLE res AS INTEGER NO-UNDO.
res = tests.base_language.builtin_functions.accepted_params.dynamic_invoke.support.HelperClassReturningExtents:static1DArgsIntRetInt(h1::nestedTmpTbl1::nestedTmpTbl2::num) NO-ERROR.
AssertExt:NotErrorNotWarning().
Assert:Equals(3, res).
END PROCEDURE.
Converts to:
res.assign(ObjectOps.invokePoly(com.goldencode.testcases.tests.base_language.builtin_functions.accepted_params.dynamic_invoke.support.HelperClassReturningExtents.class, "static1DArgsIntRetInt", "I", h1.unwrapDereferenceable().dereference(handle.class, "nestedTmpTbl1").unwrapDereferenceable().dereference(handle.class, "nestedTmpTbl2").unwrapDereferenceable().dereference("num"))));
I tried to convert both with and without the mod in the builtin_function.rules and with the mod the code passes compilation and the test passes.
#66 Updated by Constantin Asofiei over 1 year ago
Thanks, go ahead with committing this change and reconvert that project .
#67 Updated by Paul Bodale over 1 year ago
- % Done changed from 10 to 90
Constantin Asofiei wrote:
Thanks, go ahead with committing this change
Commited on 6490a, rev. #15822.
Currently working on another problem found. The following code:
CREATE tt. ASSIGN tt.field1 = charVarStoringClassName tt.field2 = DYNAMIC-INVOKE(charVarStoringClassName, "aMethod", "anArgument").
Converts to:
tt.setField1(charVarStoringClassName); tt.setField2(new character(ObjectOps.dynamicInvoke(charVarStoringClassName, "aMethod", "I", "anArgument")));
Compilation fails as there is no character constructor that takes an Object as parameter.
#68 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Compilation fails as there is no character constructor that takes an Object as parameter.
Do you have tests with both extent fields and scalar table fields, while RVALUE is POLY?
I think this may need to be converted as :: in FWD, to act like a dynamic field assignment.
#69 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Do you have tests with both extent fields and scalar table fields, while RVALUE is POLY?
No, I've omitted those. Working on a batch of tests right now.
I have tests for the ASSIGN statement which pass 100%.
#70 Updated by Paul Bodale over 1 year ago
Tests are ready.
Constantin Asofiei wrote:
I think this may need to be converted as
::in FWD, to act like a dynamic field assignment.
Aside from the tests, I declared handle variable referencing the field by :: and it works. The problem is that the TEMP-TABLE in question statically defined (defined with DEFINE TEMP-TABLE ...). How should I make the change?
#71 Updated by Constantin Asofiei over 1 year ago
Check how this gets converted and tt1.f1 = i should be something similar.
def temp-table tt1 field f1 as int. def var i as int. temp-table tt1::f1 = i. // use dynamic-invoke instead
#72 Updated by Constantin Asofiei over 1 year ago
Make also tests with DEFINE BUFFER b1 for book. b1.f1 = ..., book.isbn = ..., def buffer b2 for tt1. b2.f1 = ... (I mean, not just fields referenced via temp-tables).
#73 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
Make also tests with
DEFINE BUFFER b1 for book. b1.f1 = ...,book.isbn = ...,def buffer b2 for tt1. b2.f1 = ...(I mean, not just fields referenced via temp-tables).
OK, is it ok to declare the buffers as referencing a temp-table or should they reference an actual table from a database?
#74 Updated by Constantin Asofiei over 1 year ago
Paul Bodale wrote:
Constantin Asofiei wrote:
Make also tests with
DEFINE BUFFER b1 for book. b1.f1 = ...,book.isbn = ...,def buffer b2 for tt1. b2.f1 = ...(I mean, not just fields referenced via temp-tables).OK, is it ok to declare the buffers as referencing a temp-table or should they reference an actual table from a database?
Both temp-table and database table.
#75 Updated by Paul Bodale over 1 year ago
I did some tests and found that the behavior of assigning viaI think this may need to be converted as
::in FWD, to act like a dynamic field assignment.
:: is different from the one via the simple dot.For example:
tt.ext3 = DYNAMIC-INVOKE(clsRef, "retVar")works as expected while using::prints the errors 26, 142, 3131 and 142tt.scalar = DYNAMIC-INVOKE(clsRef, "retVarChar")raises the errors 76 and 5729 one after another while using::prints the errors 76, 76, 5729, 3131 and 142tt.scalar = DYNAMIC-INVOKE(clsRef, "retExt3")raises the error 14905 while using::prints the errors 14910, 3131 and 142
And many other cases...
#76 Updated by Constantin Asofiei over 1 year ago
OK, then please add this to the list of (edge) cases to implement.
#77 Updated by Paul Bodale over 1 year ago
Constantin Asofiei wrote:
OK, then please add this to the list of (edge) cases to implement.
So what do we resolve the initial problem? tt.field2 = DYNAMIC-INVOKE(charVarStoringClassName, "aMethod", "anArgument").
tt.ext3 = DYNAMIC-INVOKE(clsRef, "retVar")works; referencing by :: failstt.ext3 = DYNAMIC-INVOKE(clsRef, "retUnknown")works; referencing by :: fails
#78 Updated by Paul Bodale over 1 year ago
Status as of March 31¶
- All tests except assigning to buffers are passing conversion and compilation phases
- Here are the known problems and unimplemented features:
- Arithmetic ops:
- In progress the expression
1 / DYNAMIC-INVOKE(clsRef, "returnExtent")is valid and returns?when assigned to a variable and an array of?when assigned to an extent. FWD doesn't currently support this. var1 / var2 / DYNAMIC-INVOKE(clsRef, "voidMethod")returns?BUTvar1 / DYNAMIC-INVOKE(clsRef, "voidMethod")andDYNAMIC-INVOKE(clsRef, "voidMethod") / var1throw error. FWD throws error in the first case but behaves correctly on the other 2.- Wrong errors are thrown for some cases of -, + and * operations.
- In progress the expression
- Block statements:
DO WHILE DYNAMIC-INVOKE(clsRef, "retVarChar")doesn't throw error but it should.REPEAT WHILE DYNAMIC-INVOKE(clsRef, "retVarChar")doesn't throw error but it should.
- Call types:
- (object method)
SetParameter(1, "INTEGER", "INPUT", DYNAMIC-INVOKE(clsRef, "returnExtent3"))(object method) doesn't throw error but it should. - SetParameter methods have different behavior the object based one throwing error on the spot when trying to set a parameter not declared as extent and the other one throwing the error at the invocation call.
[...] Match on wrong method results in no error being thrown.- calling internal procedures in some cases results in the wrong errors.
- calling methods in some cases results in the wrong errors.
- (object method)
- Widgets/Handles:
- (this is non-related) Dynamic combo-box doesn't have the same behavior.
- Logical ops:
DYNAMIC-INVOKE(clsRef, "returnExtent") AND logicalVaris a valid logical expression and it seems like the call is considered to beFALSE. FWD fails.- Same for the
ORoperator. IF DYNAMIC-INVOKE(clsRef, "returnExtent") THENis a valid statement and it evaluates toFALSE. FWD fails.IF NOT DYNAMIC-INVOKE(clsRef, "returnExtent") THENis a valid statement and it evaluates toTRUE. FWD fails.
- Return statement:
- Unimplemented behavior of error handling. Some errors are thrown to the caller and stop execution, others are not thrown to the caller and handled in the CATCH block and others are thrown to the caller and also handled in the CATCH block.
- Wrong behavior of functions in some cases.
- Wrong behavior of methods in some cases.
DYNAMIC-FUNCTIONreturning extent is not implemented.
- Dynamic property:
chained:call:to:extent3Property = DYNAMIC-INVOKE(clsRef, "returnCharExtent3")not throwing errorStatic:extent3Property = DYNAMIC-INVOKE(clsRef, "returnCharExtent3")not throwing error- wrong errors being thrown in some cases
- Assignments
- Assignments to database tables, buffers and TEMP-TABLES except when using :: result in the failing of compilation phase.
#79 Updated by Paul Bodale over 1 year ago
Tests for this task are found in the testcases project (xfer server) following this path:
./tests/base_language/builtin_functions/accepted_params/dynamic_invoke/
To run the tests, zfile_set.txt should be:
F ./support/test/assert_equals.i F ./support/FileUtils.cls F ./support/test/AssertExt.cls D ./tests/base_language/builtin_functions/accepted_params/dynamic_invoke/ (*.p|*.cls)
#80 Updated by Paul Bodale about 1 year ago
While working on another task, I encountered a problem that applies to the changes I made for DYNAMIC-INVOKE and modified the rules accordingly. The problem was about matching on function parameters. If the function was returning an extent then the structure wouldn't be as expected and would result in errors or wrong API conversion for DYNAMIC-INVOKE.
To avoid this, I patched a function wrote in common-progress.rules from the 9877a branch, rev. 15859 and used it to retrieve the correct corresponding parameter.
I committed the changes and rebased the branch with the trunk rev. 15866
#81 Updated by Constantin Asofiei about 1 year ago
rules/convert/base_structure.xml- shouldn't this call
is_extent_or_poly_casefromcommon-progress.rules? Asis_extent_or_poly_caseis not called from anywhere else at this time; if is meant to be called in the future, then changebase_structure.xmlto callis_extent_or_poly_case
- shouldn't this call
src/com/goldencode/p2j/util/ControlFlowOps.java- there is a
TODO: Trying to match methods with the wrong extent?- is this meant to be fixed now? - there is
TODO: one more check for extent parameters- is this meant to be fixed now? CacheKeyneeds javadoc for the new parameter
- there is a
src/com/goldencode/p2j/util/ObjectOps.java- again there are some TODOs - are they meant to be fixed now?
src/com/goldencode/p2j/util/PropertyReference.javasetExtent(BaseDataType[] source)needs javadoc
src/com/goldencode/p2j/util/SourceNameMapper.javafindLegacyAccessorneeds javadoc for the new parameter- rename
findLegacyAccessorstofindLegacySetters, as this is what it returns
- please add history entries to all files and double-check the copyright year
- run
javadocand fix errors only in the files changed in this branch - please rebase
#82 Updated by Constantin Asofiei about 1 year ago
Paul, please get the review addressed, rebase and lets get it through testing.
#83 Updated by Paul Bodale about 1 year ago
Committed rev. 15887 on branch 6490a that addresses the feedback.
Constantin Asofiei wrote:
Review for 6490 rev 15875:
rules/convert/base_structure.xml
- shouldn't this call
is_extent_or_poly_casefromcommon-progress.rules? Asis_extent_or_poly_caseis not called from anywhere else at this time; if is meant to be called in the future, then changebase_structure.xmlto callis_extent_or_poly_case
I wrote the function as requested but there is no need for it. Instead I wrote another one similar to it, get_dynamic_case, but that returns just the type of API to use for DYNAMIC-INVOKE and DYNAMIC-PROPERTY.
src/com/goldencode/p2j/util/ControlFlowOps.java
- there is a
TODO: Trying to match methods with the wrong extent?- is this meant to be fixed now?
No, the comment is regarding the process of method resolution and it this is out of scope for this task. I added the comment to remind of the fact that the execution could in theory reach that point in those cases and that needs to be handled.
- there is
TODO: one more check for extent parameters- is this meant to be fixed now?
This was left by mistake.
CacheKeyneeds javadoc for the new parameter
src/com/goldencode/p2j/util/ObjectOps.java
- again there are some TODOs - are they meant to be fixed now?
This falls into the category of cases that are handled correctly but the error is wrong.
#84 Updated by Paul Bodale about 1 year ago
This problem remained unresolved:
CREATE tt. ASSIGN tt.field1 = charVarStoringClassName tt.field2 = DYNAMIC-INVOKE(charVarStoringClassName, "aMethod", "anArgument").
Converts to:
tt.setField1(charVarStoringClassName); tt.setField2(new character(ObjectOps.dynamicInvoke(charVarStoringClassName, "aMethod", "I", "anArgument")));
We talked about converting . reference to a :: reference but I found cases where the behavior is not the same, see notes #6490-75 and #6490-77.
#85 Updated by Constantin Asofiei about 1 year ago
What is the behavior in trunk? Also, ObjectOps.dynamicInvoke now returns 'object' or BDT?
#86 Updated by Paul Bodale about 1 year ago
ObjectOps.dynamicInvoke is the API for poly, it returns object. Trunk doesn't have the methods for returning extents or poly yet so it would just default to the API returning BDT.
ObjectOps.invoke()-> returns BDTObjectOps.invokeExtent()-> returns extent BDTObjectOps.dynamicInvoke()-> returns Object
We decided that assignments should convert to poly.
#87 Updated by Constantin Asofiei about 1 year ago
Is it 'safe' enough to emit invoke() if LVALUE is a scalar field, and invokeExtent otherwise? Ensuring that an ERROR condition (although not necessarily the right code) is being thrown, if DYNAMIC-INVOKE is not returning scalar/extent, as expected?
#88 Updated by Paul Bodale about 1 year ago
There is the case where LVALUE is an extent and RVALUE is a scalar:
intExt3 = 1. //sets all the array elements to 1 intExt3 = DYNAMIC-INVOKE(clsRef, "retScalar").
- When assigning to a scalar variable, the only case that won't raise error is when the RVALUE is also scalar (and the same type).
- When assigning to an extent variable, RVALUE can be both scalar and extent.
I think we probably would be better to convert DYNAMIC-INVOKE to the poly type when LVALUE is a extent, and the rest to BDT. ArrayAssigner.AssignMultiPoly already handles this cases and it supports the Object argument.
I'll check to see if there is another place where we can raise the proper error for BDT variables that are trying to be assigned to an extent. I think maybe the assign method can work.
#89 Updated by Paul Bodale about 1 year ago
Committed rev. 15911 on branch 6490a that addresses the issue from note #6490-84
I changed the rules so that DYNAMIC-INVOKE would be converted to the version returning poly only when lvalue is a reference to an unsubscripted extent and to BTD for the rest. Testing turned out ok except for the wrong errors being thrown.
#90 Updated by Paul Bodale about 1 year ago
Committed rev. 15912 on branch 6490a in which I modified "is_extent_lvalue_ref" function because it wasn't returning the correct results when lvalue is a field type.
This case:
DEFINE TEMP-TABLE tt NO-UNDO
FIELD field1 AS CHARACTER
FIELD field2 AS INTEGER EXTENT 3
FIELD field3 AS INTEGER EXTENT 3.
CREATE tt.
ASSIGN
tt.field1 = DYNAMIC-INVOKE(charVarStoringClassName, "aMethod", "anArgument").
tt.field2 = DYNAMIC-INVOKE(charVarStoringClassName, "aMethod", "anArgument").
tt.field3[1] = DYNAMIC-INVOKE(charVarStoringClassName, "aMethod", "anArgument").
Now converts to this:
tt.create(); tt.setField1(new character(ObjectOps.invoke(charVarStoringClassName, "aMethod", "I", "anArgument"))); tt.setField2(new integer(ObjectOps.dynamicInvoke(charVarStoringClassName, "aMethod", "I", "anArgument"))); tt.setField3(0, new integer(ObjectOps.invoke(charVarStoringClassName, "aMethod", "I", "anArgument")));
Note that the second assignment results in failing the compilation phase because of the constructor and the fact that currently there is no support for the poly case when assigning to an extent field type.
#91 Updated by Paul Bodale about 1 year ago
Rebased branch 6490a with trunk rev. 15908.
#92 Updated by Paul Bodale about 1 year ago
While making some tests for another task I found that the cast to Object for function calls was not emitting anymore. The rule that generated it somehow got lost when I rebased/updated the branch.
I committed rev. 15922 on branch 6490a that addresses this issue.
Talking about this, we never discussed whether to do the same for method calls. Should we?
#93 Updated by Paul Bodale about 1 year ago
Rebased branch 6490a with trunk rev. 15918
#94 Updated by Constantin Asofiei about 1 year ago
- % Done changed from 90 to 100
- Status changed from WIP to Merge Pending
Please merge to trunk now. I've ran conversion again and it was OK.
#95 Updated by Paul Bodale about 1 year ago
- Status changed from Merge Pending to Test
Branch 6490a was merged into trunk as rev. 15921 and archived.
#96 Updated by Paul Bodale about 1 year ago
- % Done changed from 100 to 80
- Status changed from Test to WIP
Status as of May 29¶
- All tests except assigning to buffers are passing conversion and compilation phases- Here are the known problems and unimplemented features:
- Arithmetic ops:
- In progress the expression
1 / DYNAMIC-INVOKE(clsRef, "returnExtent")is valid and returns?when assigned to a variable and an array of?when assigned to an extent. FWD doesn't currently support this. var1 / var2 / DYNAMIC-INVOKE(clsRef, "voidMethod")returns?BUTvar1 / DYNAMIC-INVOKE(clsRef, "voidMethod")andDYNAMIC-INVOKE(clsRef, "voidMethod") / var1throw error. FWD throws error in the first case but behaves correctly on the other 2.- Wrong errors are thrown for some cases of -, + and * operations.
- In progress the expression
- Block statements:
DO WHILE DYNAMIC-INVOKE(clsRef, "retVarChar")doesn't throw error but it should.REPEAT WHILE DYNAMIC-INVOKE(clsRef, "retVarChar")doesn't throw error but it should.
- Call types:
- (object method)
SetParameter(1, "INTEGER", "INPUT", DYNAMIC-INVOKE(clsRef, "returnExtent3"))(object method) doesn't throw error but it should. - SetParameter methods have different behavior the object based one throwing error on the spot when trying to set a parameter not declared as extent and the other one throwing the error at the invocation call.
[...] Match on wrong method results in no error being thrown.- calling internal procedures in some cases results in the wrong errors.
- calling methods in some cases results in the wrong errors.
- (object method)
- Widgets/Handles:
- (this is non-related) Dynamic combo-box doesn't have the same behavior.
- Logical ops:
DYNAMIC-INVOKE(clsRef, "returnExtent") AND logicalVaris a valid logical expression and it seems like the call is considered to beFALSE. FWD fails.- Same for the
ORoperator. IF DYNAMIC-INVOKE(clsRef, "returnExtent") THENis a valid statement and it evaluates toFALSE. FWD fails.IF NOT DYNAMIC-INVOKE(clsRef, "returnExtent") THENis a valid statement and it evaluates toTRUE. FWD fails.
- Return statement:
- Unimplemented behavior of error handling. Some errors are thrown to the caller and stop execution, others are not thrown to the caller and handled in the CATCH block and others are thrown to the caller and also handled in the CATCH block.
- Wrong behavior of functions in some cases.
- Wrong behavior of methods in some cases.
DYNAMIC-FUNCTIONreturning extent is not implemented.
- Dynamic property:
chained:call:to:extent3Property = DYNAMIC-INVOKE(clsRef, "returnCharExtent3")not throwing errorStatic:extent3Property = DYNAMIC-INVOKE(clsRef, "returnCharExtent3")not throwing error- wrong errors being thrown in some cases
- Assignments
- Assignments to database tables, buffers and TEMP-TABLES except when using :: result in the failing of compilation phase.
#97 Updated by Roger Borrello about 1 year ago
Have you been alerted to the #7143-1555 regression? This affects the ReportDriver for Hotel and Hotel GUI. The below is from Hotel is so much smaller then Hotel GUI.
The ReportDriver gives a NPE error getting the "extent" annotation.
Show NPE
Perhaps the "extent" is just a red herring, but I thought I'd report it. I am using trunk-16003 and it occurs whether I have <parameter name="denorm-extents" value="true" /> or <parameter name="expand-extents" value="false" /> or nothing.
The front conversion works fine. Perhaps there's an annotation not added until a later phase that the ReportDriver is expecting?
#98 Updated by Alexandru Lungu about 1 year ago
- Related to Bug #10215: ant rpt fails when running only conversion.front added
#99 Updated by Constantin Asofiei about 1 year ago
Paul - please create a separate task in Base Language project, for the edge cases in #6490-96.
#100 Updated by Paul Bodale about 1 year ago
#101 Updated by Constantin Asofiei 12 months ago
- Related to Bug #10315: Address edgecases for DYNAMIC-INVOKE added
#102 Updated by Constantin Asofiei 12 months ago
- Status changed from WIP to Closed
- % Done changed from 80 to 100