Page 1 of 1

Type Extensions Across Modules

Posted: Fri Apr 12, 2019 2:56 am
by gray
Let's assume we have this module as starting point:

Code: Select all

MODULE M1;

  TYPE
    T1* = POINTER TO T1Desc;
    T1Desc* = RECORD
      i: INTEGER
    END;
     
  PROCEDURE P1*(t: T1);
  END P1;
  
  PROCEDURE Init*(t: T1);
  BEGIN 
    t.i := 13
  END Init;
  
END M1.
Now I want to extend the type as follows:

Code: Select all

MODULE M2;

  IMPORT M1;
  
  TYPE
    T2* = POINTER TO T2Desc;
    T2Desc* = RECORD(M1.T1Desc)
      j: INTEGER
    END;
     
  PROCEDURE P2*(t: T2);
  BEGIN 
    t.i := 4 (*problem*)
  END P2;
  
  PROCEDURE Init*(t: T2);
  BEGIN
    t.j := 17
  END Init;

END M2.
The compiler flags the assigment in 'P2' as error (undef). Of course I can export 'i' in M1, but that would be contrary to the concepts of encapsulation andprotected modularity. Also, if I am not the author of M1, and/or don't have the source code, this is not even an option.

If I put all the code into one module...

Code: Select all

MODULE M3;

  TYPE
    T1* = POINTER TO T1Desc;
    T1Desc* = RECORD
      i: INTEGER
    END;
    
    T2* = POINTER TO T2Desc;
    T2Desc* = RECORD(T1Desc)
      j: INTEGER
    END;
    
  PROCEDURE P1*(t: T1);
  END P1;
  
  PROCEDURE P2*(t: T2);
  BEGIN 
    t.i := 4
  END P2;
  
  PROCEDURE InitT1*(t: T1);
  BEGIN 
    t.i := 13
  END InitT1;

  PROCEDURE InitT2*(t: T2);
  BEGIN 
    t.j := 17
  END InitT2;

END M3.
... the compiler happily accepts the assignment, ie. it "knows" that 'i' is an element of the extended type.

To me, 1) this does not seem consistent, and, 2) in the case of the separated modules, is a real limitation. The author of a module should not need to "prepare" possible future extensions, aside from the problems with exporting implementation details of a type.

Is this a limitation of the language or the compiler? Or do I miss something here?

(FWIW, the practical use case here is a serial USART module that I want to extend with a data transmission functionality using a buffer and interrupt-driven sending.)

Re: Type Extensions Across Modules

Posted: Fri Apr 12, 2019 9:41 am
by cfbsoftware
If you want to access the field i directly outside M1 then you must mark it as exported. If you want to keep i hidden but change its value outside of M1 then you can implement an exported “setter” procedure in M1 e.g.

Code: Select all

(* untested *)
PROCEDURE SetHiddenField*(t: T1; val: INTEGER);
BEGIN
  t.i := val 
END SetHiddenField;

Re: Type Extensions Across Modules

Posted: Fri Apr 12, 2019 1:57 pm
by gray
Oh, that's a bit disappointing. In my book, being able to extend a base type without the need to change this base type scores high. Again, is this a limitation of the language or the compiler?

Here's a loosely related follow-up issue I ran into today:

Code: Select all

MODULE M1;

  TYPE
    T1 = POINTER TO T1Desc;
    T1Desc = RECORD
    END;

  VAR
    ta: ARRAY 4 OF T1;
    t: T1;
    
BEGIN
  CASE ta[0] OF (* 1: does not compile: invalid type *)
    T1:
  END;
  
  IF ta[0] IS T1 THEN END; (* 2: does not compile: compiler error; reg stack not empty *)
  
  t := ta[0];
  CASE t OF   (* 3: compiles *)
    T1:
  END

END M1.
I was baffled by these errors (cases 1 & 2), as the code seems innocuous and straightforward. Any advice here, apart from using workaround 3?

Re: Type Extensions Across Modules

Posted: Sat Apr 13, 2019 4:14 am
by cfbsoftware
gray wrote:Oh, that's a bit disappointing. In my book, being able to extend a base type without the need to change this base type scores high. Again, is this a limitation of the language or the compiler?
There appears to be some misunderstanding. You can certainly extend a base type without the need to change the base type. What you can't do is change the interface of a module from within an importing module.

A programmer is able to extend any type exported from a module with or without access to the source code. Without the source code of M1 you would have no knowledge of the existence of i. If the author of M1 determines that T1.i is private then he can make any change he likes to T1 (remove i, change the type of i etc.) without fear of invalidating any extensions to T1. I wouldn't want it to be any other way and am having difficulty understanding why you would. Do you have a real-world example to illustrate what it is you are trying to do?

At first sight the second part of your message appears to be a bug. I've relocated it to the 'Bug Reports' topic.

Re: Type Extensions Across Modules

Posted: Tue Apr 16, 2019 12:35 am
by gray
cfbsoftware wrote:A programmer is able to extend any type exported from a module with or without access to the source code. Without the source code of M1 you would have no knowledge of the existence of i. If the author of M1 determines that T1.i is private then he can make any change he likes to T1 (remove i, change the type of i etc.) without fear of invalidating any extensions to T1. I wouldn't want it to be any other way and am having difficulty understanding why you would. Do you have a real-world example to illustrate what it is you are trying to do?
Indeed, I understand your reasoning and the benefits of this strict separation. My points and arguments were not even consistent, I guess...

My real-world application is a USART device driver (Serials.mod), with an extension for interrupt-driven transmission based on a circular buffer (SerialsB.mod), where the CR1 and DR registers' addresses stored in the base type are also needed in the extended type.

Code: Select all

MODULE Serials;

  TYPE
    Serial* = RECORD
      ...
      DR : INTEGER;
      CR1: INTEGER;
      ...
    END;

  PROCEDURE* configUSART(s: Serial);
  BEGIN
    SYSTEM.PUT(s.CR2, {});
    SYSTEM.PUT(s.CR3, {});
    SYSTEM.PUT(s.CR1, {UE, RE, TE})
  END configUSART;

END Serials.

Code: Select all

MODULE SerialsB;

  TYPE
    SerialB* = RECORD(Serials.Serial)
      ...
      DRb: INTEGER;
      CR1b: INTEGER;
      ...
    END;
    
  PROCEDURE* configUSART(s: SerialB);
    VAR
      x: SET;
  BEGIN
    SYSTEM.GET(s.CR1b, x);
    SYSTEM.PUT(s.CR1b, x + {TXEIE})
  END configUSART;

END SerialsB.
As I see it, there are two possibilities: 1) export the data from the base type, or 2) data redundancy. I currently use redundancy, it's only two INTEGERs (DRb is used by the interrupt handler to transmit the data, not shown here).
But maybe I miss another alternative approach?

Re: Type Extensions Across Modules

Posted: Sun Apr 21, 2019 12:12 am
by cfbsoftware
Serials.configUSART would be more appropriately named Serials.EnableUSART. This should be separated from the code that just configures the registers. You could then include one or more additional exported procedures in your base module that allowed configuration of the registers (e.g. add / remove bits like TXEIE) without having to know the names / addresses of the registers in the base module.