Reputation: 1863
Does anyone know why there's an error on the indicated lines?
Seems like a compiler bug according to the Type Compatibility page. Wildcard is T1, and TStringWildcard2 is T2.
Also according to this
An expression of type T2 can be assigned to a variable of type T1 if the value of the expression falls in the range of T1 and at least one of the following conditions is satisfied: T1 is the IUnknown or IDispatch interface type and T2 is Variant or OleVariant. (The variant's type code must be varEmpty, varUnknown, or varDispatch if T1 is IUnknown, and varEmpty or varDispatch if T1 is IDispatch.)
If T1 is IUnknown and T2 is OleVariant, an expression of type T2 can be assigned to a variable of type T1 - therefore the second error line should compile as well.
ACollection2 := ACollection1; // theoretically should compile, but doesn't. Since LWildcard := TStringWildcard2.Create (compiles, since LWildcard is declared as a variable of the Wildcard interface, and TStringWildcard2 implements the Wildcard interface).
As such, therefore, ACollection2 (TDictionary<string, Wildcard> ) := ACollection1; (TDictionary<string, TStringWildcard2>) should compile right?
program TestCollections;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Generics.Collections;
type
Wildcard = interface
['{941128E5-D87C-4E3E-98D8-CF45EE6FEC09}']
procedure Clear;
end;
Wildcard<T> = interface(Wildcard)
function getMatch: T;
end;
TParent = class(TInterfacedObject)
end;
TStringWildcard1 = class(TParent, Wildcard<string>)
function getMatch: string;
procedure Clear;
end;
TStringWildcard2 = class(TParent, Wildcard, Wildcard<string>)
function getMatch: string;
procedure Clear;
end;
{ TStringWildcard1 }
procedure TStringWildcard1.Clear;
begin
end;
function TStringWildcard1.getMatch: string;
begin
Result := 'String match 1';
end;
{ TStringWildcard2 }
procedure TStringWildcard2.Clear;
begin
end;
function TStringWildcard2.getMatch: string;
begin
Result := 'String match 2';
end;
var
LWildcard: Wildcard;
ACollection1: TDictionary<string, TStringWildcard2>;
ACollection2, ACollection3: TDictionary<string, Wildcard>;
ACollection4: TDictionary<string, IUnknown>;
begin
LWildcard := TStringWildCard2.Create; // <-- TStringWildcard2 compatible with Wildcard
// Since TStringWildcard2 is compatible with Wildcard, therefore,
// ACollection1 should be compatible with ACollection2
ACollection1 := TDictionary<string, TStringWildcard2>.Create;
ACollection2 := ACollection1;
ACollection4 := TDictionary<string, OleVariant>.Create;
end.
Upvotes: 0
Views: 617
Reputation: 613461
There is no compiler bug here. The compiler is behaving as designed. The assignments fail because the types are indeed not compatible. Delphi generic types are invariant.
The documentation says:
Two instantiated generics are considered assignment compatible if the base types are identical (or are aliases to a common type) and the type arguments are identical.
Now, let's look at the first assignment that fails:
var
ACollection1: TDictionary<string, TStringWildcard2>;
ACollection2: TDictionary<string, Wildcard>;
....
ACollection2 := ACollection1;
This fails because the type arguments are not identical.
And for ACollection4
we have
var
ACollection4: TDictionary<string, IUnknown>;
....
ACollection4 := TDictionary<string, OleVariant>.Create;
Again, the type arguments are not identical.
There's a very good reason for the language designers choosing to make generic types invariant. Consider the following example.
type
TClass1 = class(TObject)
end;
TClass2 = class(TClass1)
end;
var
List1: TList<TClass1>;
List2: TList<TClass2>;
....
List2 := TList<TClass2>.Create;
List1 := List2; // does not compile, but let's imagine that it did
List1.Add(TClass1.Create);
Since List1
and List2
are the same object, we have now succeeded in putting an object of type TClass1
into List2
which breaks the type system.
In fact your attempt to assign
ACollection2 := ACollection1;
illustrates this very issue. Suppose that assignment was valid and then you added to ACollection2
something that implemented Wildcard
but that was not TStringWildcard2
. Then that same thing would have been added to ACollection1
and all of a sudden you'd succeeded in adding to ACollection1
something that was not TStringWildcard2
and you've broken the type system.
In languages that support generic variance there need to be runtime checks to stop this happening. As it happens, only yesterday our very own Jon Skeet blogged on this very topic: Array covariance: not just ugly, but slow too. So, be careful what you wish for!
Because of this the Delphi designers elected to make Delphi generic types invariant.
Upvotes: 4
Reputation: 60276
It's not a bug, it's a reflection you made that is wrong. Using a compatible type as a generic type argument does not make the generic type compatible as well. This has to do with covariance and contravariance.
Upvotes: 1