Extend TTypeSymbol with support for two-way type conversion

Issue #94 new
Anders Melander created an issue

The current type conversion scheme, using TTypeSymbol.IsCompatible and TConvExpr.WrapWithConvCast, is only able to convert a value if the target type knows about the source type.

Consider the following example. WORD is a custom type derived from TBaseIntegerSymbol, implemented by the class TWinApiTypeIntegerSymbol<WORD>:

var i: integer := 1; // TBaseIntegerSymbol

// TWinApiTypeIntegerSymbol knows how to convert from TBaseIntegerSymbol
var w: WORD := i;

// TBaseIntegerSymbol doesn't know how to convert from TWinApiTypeIntegerSymbol
// Syntax Error: Incompatible types: Cannot assign "WORD" to "Integer"
var n: integer := w;

Because of this design it appears to be impossible to extend the type system with custom types that support implicit conversion. I'm aware that it is possible to work around the limitation with variants or explicit casts, but it's a bit hard to explain to the users why they need to cast value types that are clearly compatible.

Proposal

Extend TTypeSymbol with support for reverse type compatibility check.

type
  TTypeSymbol = class(...)
    ...
    function IsCompatible(typSym : TTypeSymbol) : Boolean; virtual; deprecated 'Use CanConvertFrom';
    function CanConvertTo(typSym : TTypeSymbol) : Boolean; virtual;
    function CanConvertFrom(typSym : TTypeSymbol) : Boolean; virtual;
    ...
  end;

function TTypeSymbol.CanConvertTo(typSym : TTypeSymbol) : Boolean;
begin
  Result := False;
end;

function TTypeSymbol.CanConvertFrom(typSym : TTypeSymbol) : Boolean; virtual;
begin
  Result := IsCompatible(typSym); // backward compatible
end;

Decouple type conversion from TConvExpr

Instead of having the type conversion rules hard coded into TConvExpr I propose the logic be delegated to the individual type classes.

type
  TTypeSymbol = class(...)
    ...
    function ConvertTo(typSym : TTypeSymbol) : TTypedExpr; virtual;
    function ConvertFrom(typSym : TTypeSymbol) : TTypedExpr; virtual;
    ...
  end;

See also: MSDN: Type Converters for Value Translation


Example (script): ###

var a: TypeA;
var b: TypeB;

a := b;

Usage (pseudo code):

  if (a.CanConvertFrom(b)) then
    a.ConvertFrom(b)
  else
  if (b.CanConvertTo(a)) then
    b.ConvertTo(b)
  else
    error;

Although I "like" the architecture of the above proposal a much more lean solution might be to simply add support for implicit type conversion operators. This would also have the added benefit of being able to add implicit type conversion between existing types.

Comments (2)

  1. Eric Grange repo owner

    Yes, the whole conversion framework needs to be redone to support implicit casts, the current mechanism was more an incremental tweak over the previous architecture.

    However rather than handling it at the TTypeSymbol level, I was planning to handle it at the operator level, because once you allow custom implicit cast operators, you introduce scoping and precedence issues, ie. the conversion no longer just depends on the types to/from, but on the scope, and all casters in scope.

    There are already some tidbits ready, in TOperators class (RegisterCaster/FindCaster), what's missing:

    1. the ability to flag a caster as implicit (trivial)
    2. use the caster logic in place of the WrapConvCast and similar methods (not anticipating issues for that)
    3. figure out the precedence rules Delphi uses, ie. when multiple implicit casters apply, which one is picked? The most scope-specific one? The most type-specific one? (help welcome here)
    4. units tests to ensure point 3 :) (help welcome there as well)

    Also the IsCompatible method was not meant not denote the ability to convert, but that one type is compatible with another without conversion (in your case, the Integer -> Word types would not be compatible, because you will probably want a range checking, which is a minimalistic conversion).

    However IsCompatible would be affected by implicit casts, if only to check that "implicitly compatible types" (fi. a type and an alias of that type) do not have a custom implicit caster that mean a conversion is now required.

  2. Log in to comment