Why doesn't calling a function with identical signatures in different units result in compiler error?
Asked Answered
E

2

5

Why doesn't this code result in a compiler error? I would have expected error for example 'ambiguous call to "CallMe"'. Is this a bug in the compiler or in the language? This can worked around by using the unit name and a dot in front of the function call but this not shield user code and library code against name collisions. You think that your code did something but it did something else and that's bad.

uses
  Unit2, Unit3;

{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(IntToStr(CallMe(5)));
end;

unit Unit2;
{$mode objfpc}{$H+}
interface
uses
  Classes, SysUtils;
function CallMe(A: Integer) : Integer;
implementation
function CallMe(A: Integer) : Integer;
begin
  Result := A * 2;
end;
end.

unit Unit3;
{$mode objfpc}{$H+}
interface
uses
  Classes, SysUtils;
function CallMe(A: Integer) : Integer;
implementation
function CallMe(A: Integer) : Integer;
begin
  Result := A * -1;
end;
end.
Equal answered 16/9, 2015 at 9:15 Comment(9)
This is by design: It calls the one the compiler saw last during the compile. If you want to call the other one, prepend the unit name followed by a dot to the name.Serbocroatian
Thanks. I would like to know motivation behind this design. This makes an opportunity for bugs. Let's say that the main unit originally used CallMe from Unit2 then programmer B comes along and adds unit3 because he needs some function from there and does not know that he accidentally replaced CallMe with something else (imagine a long unit with lot of code). It compiles and runs. No warning no error. I would rather have a compiler error than a runtime problem and I don't want AVeryLongLibraryName.FunctionName calls and to look every call in every included unit for an ambiguous call.Equal
Any programming language makes an opportunity for bugs. The whole thing is: you have to know what you are doing. There are external tools that give you hints about those cases.Quartan
Iirc, this behaviour has been around since Turbo Pascal first introduced units in its v4. It is a racing certainty that it isn't going to change, but otoh I have never heard of the sort of "danger" you envisage actually arising.Serbocroatian
This is one of the worst features of the language. The inability to use a single symbol from a module without merging all exported symbols into the using namespace is a terrible weakness. It's easy to fix. Just allow importing of specific symbols. Pretty much every other comparable language can do this.Alectryomancy
@DavidHeffernan, you mean that you would have to specify every import from a unit interface, like: from unit xxx use someFunc?Burgh
@LURD That you would at least have the option of doing that. It would be too draconian to remove uses.Alectryomancy
@Heffernan, you're so influent so propose an extension in the grammar of the use clause, for example uses unitA: ThisSymbol, sysutils, unitB: thisSymbol;Cravens
I agree with David. Stronger, Pascal (as in the Extended Pascal standard) iirc even has some of the import related features of Modula2 backported.Phyllida
B
13

From documentation:

If two units declare a variable, constant, type, procedure, or function with the same name, the compiler uses the one from the unit listed last in the uses clause. (To access the identifier from the other unit, you would have to add a qualifier: UnitName.Identifier.)

Burgh answered 16/9, 2015 at 9:22 Comment(7)
I see. I have to say this was very surprising and in my opinnion dangerous behaviour. A later added library to a project had a function with same signature as in the old code and old code started to use the new function because the new unit was added last in the uses clauses and the new function had a bug and worked differently and that caused trouble. Do you know can this be turned into an compiler error by option so this does not happen again by accident?Equal
I have lived with this behaviour since the TP4 days, and never really had any problem with name clashes. There is no hint/warning/error option to indicate this condition. The code insight may be of help though, showing which unit the functions belongs to.Burgh
@user2304430: There is no way to turn it into an error, because it isn't an error.Sculptress
Still I think the contrived code example I gave should have been an ambigous call and caught by the compiler and force the programmer to explicitly tell to the compiler which one to call by providing unit name before the call instead of picking one that was imported later.Equal
There are tools that can make code analysis, like Pascal Analyzer. You can find duplicate identifiers using those. I just tested it on a small project and it gave about 7000 duplicates. I'm sure you can exclude parts of the RTL and perhaps overloaded functions/procedures to narrow the count.Burgh
@user2304430: You're still ignoring the fact that this is by design, and is documented behavior. There's nothing ambiguous, even in your contrived example, because the documented behavior is to resolve it to the last used unit that matches the function. The compiler sees no ambiguity, because it behaves exactly as designed (and documented) when resolving it.Sculptress
This is an important an beneficial feature, because, although it can introduce bugs, as you say, it can equally be used to remove bugs from somebody else's code that you can't modify and, before the days of helper classes, add functionality to existing classes used in the IDE. I agree, though, even though it is not an error, a warning would be nice.Disreputable
P
2

As said it is by design, the compiler loads symbols from units using a stack based approach, and parses through the stack from last loaded to first loaded to search for a symbol. Preprocessor state is directly merged into the global state though.

Cross unit overloading is an exception though. If you mark both functions with overload; directive, you get an error (bla was the name of the function in the test)

[dcc32 Error] test.dpr: E2251 Ambiguous overloaded call to 'bla'
  Unit1.pas(8): Related method: procedure bla;
  Unit2.pas(8): Related method: procedure bla;

if you have two different signatures, it will select the best matching one.

cross overloading is a newer feature, but I don't remember exactly when. My guess is D2006.

Phyllida answered 19/9, 2015 at 11:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.