You are implementing a tiny type system for a toy language with:
char
,
int
,
float
T1
,
T2
, … (stand for an unknown type)
Assume the textual syntax for a type is:
char
|
int
|
float
T
followed by one or more digits (e.g.,
T1
,
T20
)
(<type>,<type>,...,<type>)
with no restriction on nesting (e.g.,
(int,(T1,char),float)
)
Node type representationImplement a Node class/struct that can represent any type in this grammar.
toString()Implement toString() for Node that serializes the type back into the exact canonical format described above.
infer_return(pattern, concrete)Implement a function that infers what each generic (T1, T2, …) must be, given:
pattern
: a type expression that may contain generics
concrete
: a fully concrete type expression containing
no generics
(only primitives and tuples)
The function must return a mapping like {T1 -> int, T2 -> (char,float)} such that substituting generics in pattern produces exactly concrete.
pattern
is a primitive, it must equal
concrete
.
pattern
is a generic
Tk
, bind it to
concrete
unless
it was already bound to a different type (conflict).
pattern
is a tuple,
concrete
must also be a tuple of the same length, and inference recurses element-wise.
pattern=(T1,T1)
with
concrete=(int,float)
)
pattern=(T1,int)
,
concrete=(char,int)
→
{T1 -> char}
pattern=(T1,(T2,char))
,
concrete=(float,(int,char))
→
{T1 -> float, T2 -> int}
pattern=(T1,T1)
,
concrete=(int,float)
→ error (conflict)
State any assumptions about parsing/whitespace and how you represent/compare types.