Running through some of my code last night, putting them through the new XE4 compiler, threw up a real oddity: Some code that used to compile just fine, which no longer compiles in XE4 and which should not have compiled before!
It’s an odd one, because the code previously compiled – and worked – as intended, and the old “bug” that seems to have now been fixed could well have instead been embraced as a feature (although I can understand why it was not and am not suggesting it should have, just observing).
I have to point out at this juncture that there is nothing much to learn from this post I’m afraid. It is a bit of a compiler curiosity, that is all. But even so, I found it interesting and thought others might as well. And who knows, someone else might have the same problem lurking in their older library code, unbeknownst to them, in which case this might save a bit of head scratching for them.
It involves the referencing of an enum in a unit that is not referenced in the uses clause, where the reference is qualified by the unit name in which the enum is declared and where the unit containing the reference uses some other unit that in turn does use the unit that contains the enum.
Got all that ? Clear as mud ? 🙂
Ok, here’s the simplest possible test case that will show what I mean. 3 files – a DPR and 2 units. Obviously in the real project the DPR and the units involved were a sight more complex (I suspect that the “error” crept in during a refactoring where the unit containing the enum was decomposed into 3 smaller units).
In any event, here’s the sample DPR. The code doesn’t do anything, as such, it just exercises the necessary parts of the compiler:
program unused; uses Foo.Foo in 'Foo.Foo.pas'; var c: Integer; begin c := Ord(Foo.enumBAR); end.
Here we can see that the DPR uses the unit Foo.Foo, but references an enum member – enumBAR – qualified with a different unit name, Foo, which is not being “used” at all.
Here is the Foo.Foo unit:
unit Foo.Foo; interface uses Foo; implementation end.
And finally the Foo unit containing the enum declaration with the enumBAR member:
unit Foo; interface type TEnum = (enumBAR); implementation end.
This project compiles just fine in Delphi 2010, but fails in XE4 with the compiler complaining about the undeclared “Foo” identifier on the line:
c:= Ord(Foo.enumBAR);
Again, in the actual case in my actual project, the reference was not in the DPR but in another unit in the project. All units involved were in the DPR uses list, but other than that, the scenario was the same as illustrated by the sample.
The solution is obviously to simply add the required unit to the uses list, so no great shakes.
I haven’t tried the sample specifically in other versions of Delphi, but this project has previously been tested in Delphi versions 7 thru XE2, though I cannot honestly recall if it has been tested in those versions since the refactoring that might have led to this situation. The point being that I think if I were to do that testing, I would find that the behaviour in this area might have changed as a result of the introduction of SCOPEDENUMS (since this case involves a qualified enum reference, albeit unit qualified rather than type-name qualified).
Obviously whether it existed in any other version of Delphi besides 2010 or not, the previous behaviour was technically wrong. It should not have been possible to reference members of units that are not being “used”. But then again, given than “using” a unit is a formal declaration that brings the contents of that unit into scope, it is arguable that you could conceivably extend the syntax to allow formal qualification of specific members in a unit without requiring that unit to be used to bring all it’s members into scope for unqualified use.
I don’t think you should. The declaration of which units are being used is useful self-documentation imho. As I said at the outset, it is interesting to think that it might not (and apparently did not always used to be :)) necessary.
In any event, this code now compiles correctly. Or, more accurately, correctly fails to compile. 🙂
Glad they fixed that, obviously it is a bug in the unit aliasing code where XXX.YYY can be aliased as just YYY, and since you have a unit YYY already in the compiler’s tables, it passed the “defined” check (as an alias of XXX.YYY) and then grabbed the pure YYY unit first in scope.
Interesting bug, I can not imagine it came up often,
I still suspect it is more related to the scoped enums change than with unit aliasing because it only “works” with qualified enum references as far as I can tell. i.e. if you change enumBAR from an enum member to a simple constant it doesn’t compile in Delphi 2010 either (undeclared identifier ‘enumBAR’).
Oops, no. I just retested this and changing it to a constant yields the same result. However, trying to reference a type declared in the Foo unit doesn’t.
i.e. if I add:
To Foo.pas, and change the declaration of c to:
Then it doesn’t compile.
Still, I would expect some issue related to unit aliasing to apply consistently to all uses of a unit reference, whatever the qualified symbol was that followed.
As for it not coming up, it’s one of those weird ones where the “bug” potentially makes more code work more often than it should. The “fault” doesn’t result in any undesired behaviour – quite the opposite. It facilitates the desired behaviour without imposing strict compliance with the language spec, albeit almost certainly inadvertently (and inconsistently). 🙂
I haven’t bumped into this one, since I have not used this “My.Unit” style in package names.
But I did bump also into code that did not compile any more: I had mistakenly defined a procedure as a “constructor” and the compiler no longer accepted it to be called for an object instance.
I remember learning that actually as a Delphi oddity already with Delphi 2 (IIRC) that you can call the constructor again for an instance to “reinitialize” the object. I never used it much but it struck as a “nice little tweak” that might become handy at some point.
Well, I was more happy to see that opportunity go away, after all 🙂
Good to see this kind of fixes have been done as well.
Well, on the poll, XE4 is A poke in the eye for loyal customers AND Irrelevant to me AND Too little too late …
But I’d disagree with the new behaviour.
What is the point of putting FOO into the interface section of FOO.FOO other than making FOO visible when FOO.FOO is used?
How would you have a reference to Foo.enumBAR? FOO.FOO.Foo.enumBAR perhaps? So how does the compiler figure out whether FOO.FOO.Foo.enumBAR is FOO.FOO’s Foo.enumBAR, or FOO’s FOO.Foo.enumBAR or FOO.FOO.Foo’s enumBAR – any of which may or may not exist?
The very fact that FOO appears in FOO.FOO’s interface specification means that Foo.enumBAR is available to the outside world – there is NO other reason for including it in the interface – it should be used in the implementation.
Another half-baked change for XE4 – introducing yet another breakage on top of foreshadowing withdrawal of WITH and threatening to alter the basis of string indices.
The good ship Delphi’s heading for the rocks at ful speed while the captain’s busy drilling holes in the hull.
Woah. And people think I have anger issues. 🙂
Having Foo in the interface section of Foo.Foo brings the Foo members into scope for referencing in the interface (and implementation) sections of the Foo.Foo unit itself. It has – or should have – no bearing on the visibility of those Foo members for units that use Foo.Foo. If they wish to reference the Foo members as well, then they need to use Foo itself.
’twas ever thus, and rightly so.
But as I said, it does raise the interesting idea of not requiring this however, and allowing fully qualified references to unit members without requiring that unit to be explicitly used (effectively implicitly using it). This is unlikely ever to be adopted in a Pascal implementation, but it might be an interesting idea to explore over a beer one day. 🙂
Jippieh, FPC 2.7.1 does not contain the bug. 🙂 (dotted unit names are only supported in 2.7.1)
Your given example does not compile, except when I add the unit Foo to the program file as well. 🙂
Regards,
Sven
When copying unit “Foo.Foo.pas” to “Foo.Fx.pas” and to “Fy.Foo.pas” then the following DPR will compile in D7:
program unused;
uses
Foo.Fx in ‘Foo.Fx.pas’;
var
c: Integer;
begin
c := Ord(Foo.enumBAR);
end.
and the following DPR will NOT compile in D7:
program unused;
uses
Fy.Foo in ‘Fy.Foo.pas’;
var
c: Integer;
begin
c := Ord(Foo.enumBAR);
end.
So getting dotty with unit names seems to be a bad idea in Delphi versions lower than XE4