Hi,
Why is the compiler preventing me from casting an object from type a to type b when those two types are unrelated, while a dynamic-cast is working without problems?
Such a compile-time check isn't happening in c# afaik (or it might be implemented differently, see stackoverflow.com/.../compile-time-and-runtime-casting-c-sharp)
I think the compiler is incorrectly assuming this isn't a valid cast, which is proven by the dynamic-cast working without problems.
E.g.
INTERFACE IButton: END INTERFACE. CLASS ComponentFactory: METHOD PUBLIC STATIC IButton CreateButton(): RETURN NEW CustomButton(). END METHOD. END CLASS. CLASS CustomButton INHERITS System.Windows.Forms.Button IMPLEMENTS IButton: END CLASS. CLASS MyForm INHERITS Progress.Windows.Form: CONSTRUCTOR MyForm(): DEFINE VARIABLE OkButton AS IButton NO-UNDO. OkButton = ComponentFactory:CreateButton(). Controls:Add(CAST(OkButton, System.Windows.Forms.Control)). /* Isn't compiling */ Controls:Add(DYNAMIC-CAST(OkButton, "System.Windows.Forms.Control")). /* Works */ END CONSTRUCTOR. END CLASS.
Well, at least I made Mike happy this morning!
Yes, this is definitely a bug; thanks for logging it. I remember now (!!) that the compiler can sometimes determine that the CAST can never possibly work at runtime because the 2 objects bear no relationship with each other whatsoever. In that case it will fail to compile. But, for example, if one object is in the other's hierarchy, in either direction, the CAST will compile successfully. And as I said, for an interface, the compiler cannot make that judgment and should never error.
DYNAMIC-CAST uses the actual type of the object instance passed in. The compiler uses the type defined for the variable that will hold the object reference.
Actually, this is the correct behavior. The compiler only knows that OkButton is an IButton as that's how it's defined. An IButton cannot be cast to a Control. You can cast a Button to a Control because it has all the methods and and properties that Control has. But an IButton does not have any of those.
But at runtime the instance you get is an actual button which is a Conrol. The DYNAMIC-CAST is basing the cast on that, i.e., what the instance actually is not on how it's defined.
Maybe it's 'by-design' but that doesn't necessarily make it correct... since one can cast a Progress.Lang.Object to anything with no complain from the compiler and that is the root class for any living object in AVM your statement hardly holds true :(
IMHO it's just the compiler that tries to be a bit smarter... at most that would make it as a 'unchecked cast' warning and that's all.
I got used to
CAST (CAST(oSomething, Progress.Lang.Object), TheRealTargetType)
in those cases ...
Your statement "one can cast a Progress.Lang.Object to anything " is not correct. You can cast anything to a Progress.Lang.Object, but not the other way around. Think of it this way. Something defined as a P.L.O can be anything. Let's say you have a class B defined as a P.L.O. You're implying that you should be able to cast it to a C. Of course you should not be able to do that. And you can't.
yeah, it might throw a runtime error but the compiler does not complain... thanks for the crash course on OO though :)
def var o as Progress.Lang.Object.
cast(o, Progress.Lang.AppError).
My gripe is that the compiler shouldn't catch this here, it should only get caught at run-time.
Mike's example will always compile, yet not always work at run-time, and that's how I would expect CAST to work.
You should always be able to do the CAST with a type instead of needing a DYNAMIC-CAST with type name string like in this case.
But once it's referenced by a PLO you can CAST anything to anything - at least the compiler will let you. When the types are incompatible at runtime an runtime error will be thrown. And that is expected.
This behavior is needed, for instance when referencing objects in a temp-table field of type PLO. The compiler will allow any CAST from a PLO reference and it's our responsibility that the types match. Which is absolutely o.k.
So the question is really if the compiler check for CAST is too strict and a warning like suggested by Marian would be enough.
I must admit in the majority of cases, when the compiler creates an error, he is right. So I can live with the fact that when I know better I do the CAST(CAST( )) thing.
It's typically when the reference I hold to the original object is an Interface type, not a real class, that I need this.
[quote user="Mike Fechner"]
It's typically when the reference I hold to the original object is an Interface type, not a real class, that I need this.
The CAST statement is often used to check at runtime if you can cast something to something else and run conditional code if not. If the code never compiled you would not be able to do that. So that is how it is designed.
I'd rather use TYPE-OF to see if I can do things.
So maybe a bit more relaxed compile time checking, when the current reference is an Interface? But stay strict with Class references?
Equivalent code in c# works fine:
public class Form1 : Form
{
public Form1()
{
IButton myButton;
myButton = ControlFactory.CreateButton();
Controls.Add((Control)myButton);
}
}
Ah yes. Whew! And I see that I contradicted myself! The CAST should not make the compilation fail - especially when the type is an interface - since we of course won't know what that type is at runtime. And that is the point of doing the cast - to tell the compiler to shut up and let you do this because you claim to know what you're doing!
So should I escalate this one to technical support? Sounds like a valid bug?
You should log a call with support and we will discuss this internally based on your use case.
I love Laura's explanation of the CAST statement. I also tell student this is the "shut up I know what I'm doing" keyword. :-)
Case 00342730 logged, thanks.
Well, at least I made Mike happy this morning!
Yes, this is definitely a bug; thanks for logging it. I remember now (!!) that the compiler can sometimes determine that the CAST can never possibly work at runtime because the 2 objects bear no relationship with each other whatsoever. In that case it will fail to compile. But, for example, if one object is in the other's hierarchy, in either direction, the CAST will compile successfully. And as I said, for an interface, the compiler cannot make that judgment and should never error.
A type conversion like CAST(), INT() LOGICAL(), DECIMAL(), DATE(), is used to signal to the compiler that I know that data types are not compatible, but that I believe that it will be possible to convert it. The compiler say "Ok." and let it go. The runtime, on the other hand, will check again and throw an error if I was wrong.
Sometimes the compiler knows for sure it WILL NEVER WORK, and stop me:
DEF VAR AS myVar LOGICAL INIT TRUE NO-UNDO. MESSAGE DATE(myVar).
Other times the compiler knows it COULD work and leave it up to the runtime catch me if I make a mistake:
DEF VAR myVar AS CHARACTER INIT "Hi" NO-UNDO. MESSAGE DATE(myVar).
The same should be true when I use CAST(), but it is not. CAST() is inconsistent with the rest of the language. Thus I argue that this is a bug. With CAST() the compiler does not allow every version of POSSIBLE, as it should, but block us even when the CAST() might be possible.
When both the source and the target are CLASS types, the compiler knows for sure that the CAST will only succeed if either:
However, as soon as either one or both of the source and the target types is an interface, the compiler has no way to know if the runtime instance held in an interface type will be from a specific class hierarchy, or that any instance in a class type will not be a (possibly derived) type implementing the interface. It only knows it COULD be. Thus, similar to other type conversion, CAST() should signal to the compiler to "skip this check" and the runtime must double check.
For example, the compiler can stop me from trying to convert an Eagle to a Cat, or a LOGICAL to a DATE, but it cannot stop me from converting STRING to a DATE or Mammal to a Dog. It is the runtime's job to stop me if my Mammal is actually a Cat. Yet the compiler it knows Cat is not a Dog, as neither one derives from the other. This works correct.
On the other hand, it is should be up to the runtime to figure out that the Bat in IFlying can be assigned to the Mammal variable or that the Eagle in IFlying can be assigned to a Bird type, but not a Mammal type. Also, the runtime must determine that the Bat in the Mammal variable or the Eagle in the Bird variable is an IFlying but the Ostrich in the Bird variable or the Elephant in the Mammal variable are NOT IFlying. The compile cannot know and should thus allow it.
Laura,
next time you agree with me, please post before I see the thread, rather while I am hammering out a no longer needed, long winded, response. :-)
[quote user="Simon L. Prinsloo"]
Laura,
next time you agree with me, please post before I see the thread, rather while I am hammering out a no longer needed, long winded, response. :-)
And, Simon, it was such a nice example too!
I've logged defect PSC00346310 and created KB article 000067678.
[quote user="ymaisonn"]
A fix for issue PSC00346310 will be released with 11.6.2.
OpenEdge 11.6.2. is planned to be released on July 27th, subject to change.