Type System
This document describes the type system of KCL, including:
- Type rules
- Type checking
- Type conversion
- Type inference
Type Rules
Basic Definition
Assertion
All free variables of S are defined in Γ
Γ is a variable's well-formed environment, such as x1:T1, ..., xn:Tn
The assertion of S has three forms:
Environment assertion indicates that Γ is a well-formed type.
Well-formed type assertion. In the environment Γ, nat is a type expression.
Γ⊢nat Typing judgment assertion. In the environment Γ,E has the type T.
Γ⊢E:T Inference Rules
Representation
Γ⊢SΓ⊢S1,...,Γ⊢Sn In the inference rules, u, v, and w are used to represent variables, i, j, k are used to represent integers, a and b are used to represent floating point numbers, s is used to represent strings, c is used to represent literal values of constants (integers, floating point numbers, strings, boolean), f is used to represent functions, T, S, U are used to represent types.
Environment Rules
Env ⌀
⌀⊢◇ Type Definitions
Type Bool
Γ⊢booleanΓ⊢◇ Type Int
Γ⊢integerΓ⊢◇ Type Float
Γ⊢floatΓ⊢◇ Type String
Γ⊢stringΓ⊢◇ Type Literal
Γ⊢literalof(c)c∈{boolean,integer,float,string} Type List
Γ⊢listof(T)Γ⊢T T=Void Type Dict
Γ⊢dictof(Tk=T1,Tv=T2)Γ⊢T1 Γ⊢T2 T1=Void T2=Void Type Struct
Γ⊢structof(K1:T1,...,Kn:Tn)Γ⊢T1 ... Γ⊢Tn Ti=Void K1=Kn Type Union
Γ⊢unionof(T1,...,Tn)Γ⊢T1 ... Γ⊢Tn Ti=Void Type None
Γ⊢NoneΓ⊢◇ Type Undefined
Γ⊢UndefinedΓ⊢◇ Type Void
Γ⊢VoidΓ⊢◇ Type Any
Γ⊢AnyΓ⊢◇ Type Nothing
Γ⊢NothingΓ⊢◇ Typing Judgment Rules
Operand Expr
Exp Truth
Γ⊢true:booleanΓ⊢◇ Γ⊢false:booleanΓ⊢◇ Exp Int
Γ⊢int:integerΓ⊢◇ Exp Flt
Γ⊢flt:floatΓ⊢◇ Exp Str
Γ⊢str:stringΓ⊢◇ Exp None
Γ⊢none:noneΓ⊢◇ Exp Undefined
Γ⊢undefined:undefinedΓ⊢◇ Expr ListExp
Γ⊢[E1,E2,...,En]:listof sup(T1,T2,...,Tn)Γ⊢E1:T1 E2:T2 ... En:Tn Expr ListComp
Γ⊢[E1 for v in E2 if E3]:listof(T1)Γ⊢E1:T1 Γ⊢v:T Γ⊢E2:listof T Γ⊢E3:boolean Expr DictExp
Γ⊢{Ek1:Ev1,...,Ekn:Evn}:dictof(Tk=sup(Tk1,Tk2,...Tkn), Tv=sup(Tv1,Tv2,...,Tvn))Γ⊢Ek1:Tk1 Γ⊢Ev1:Tv1 ... Γ⊢Ekn:TkN Γ⊢Evn:TvN Expr DictComp
Γ⊢{E1:E2 for (v1,v2) in E3 if E4}:dictof(Tk=sup(Trk1,Trk2,...,Trkn),Tv=sup(Trv1,Trv2,...,Trvn))Γ⊢E1:Trki Γ⊢E2:Trvi Γ⊢v1:Tk Γ⊢v2:Tv Γ⊢E3:dictof(Tk, Tv) Γ⊢E4:boolean Expr StructExpr
Γ⊢{K1=E1,...,Kn=En}:structof(K1:T1,...,Kn:Tn)Γ⊢E1:T1 ... Γ⊢En:Tn K1=Kn The literal type is the value type of basic type, the union type is the combination type of types, void, any, nothing are special type references, and there is no direct value expression correspondence.
Primary Expr
Expr Index
Γ⊢E[Index]:TΓ⊢E:listof(T) Γ⊢Index:integer Expr Call
Γ⊢E1 (E2):T2Γ⊢E1:T1→T2 Γ⊢E2:T1 Expr List Selector
Γ⊢E.[Index]:TΓ⊢E:listof(T) Γ⊢Index:integer Expr Dict Selector
Γ⊢E.{S1,...,Sn}:dictof(Tk=T1,Tv=T2)Γ⊢E:dictof(Tk=T1,Tv=T2) Γ⊢S1:string ... Γ⊢Sn:string Expr Struct Selector
Γ⊢E.Ki:TiΓ⊢E:structof(K1:T1,...,Kn:Tn) Γ⊢Ki:string Unary Expr
Expr +
Γ⊢ +E:TΓ⊢E:T T∈{integer,float} Expr -
Γ⊢ −E:TΓ⊢E:T T∈{integer,float} Expr ~
Γ⊢ E:integerΓ⊢E:integer Expr not
Γ⊢ not E:booleanΓ⊢E:boolean Binary Expr
Expr op, op ∈ {-, /, %, **, //}
Γ⊢E1 op E2:TΓ⊢E1:T Γ⊢E2:T T∈{integer,float} Expr +
Γ⊢E1 + E2:TΓ⊢E1:T Γ⊢E2:T T∈{integer,float,string,listof(T1)} Expr *
Γ⊢E1 ∗ E2:TΓ⊢E1:T1 Γ⊢E2:T2 (T1==T2∈{integer,float}) or (T1==interger and T2 ∈ {string,listof(T3)}) or (T2==interger and T1 ∈ {string,listof(T3)}) Expr %
Γ⊢E1 % E2:intergerΓ⊢E1:interger Γ⊢E2:integer Expr op, op ∈ {or, and}
Γ⊢E1 op E2:booleanΓ⊢E1:boolean Γ⊢E2:boolean 示例
Expr and
Γ⊢E1 and E2:booleanΓ⊢E1:boolean Γ⊢E2:boolean Expr op, op ∈ {==, !=, <, >, <=, >=}
Γ⊢E1 op E2:booleanΓ⊢E1:T Γ⊢E2:T Expr >
Γ⊢E1 > E2:booleanΓ⊢E1:boolean Γ⊢E2:boolean Expr op, op ∈ {&, ^, ~, <<, >>}
Γ⊢E1 op E2:integerΓ⊢E1:integer Γ⊢E2:integer Expr |
Γ⊢E1 ∣ E2:TΓ⊢E1:T Γ⊢E2:T T∈{integer,listof(T1),dictof(Tk,Tv),structof(K1=T1,...,Kn=Tn)} Expr op, op ∈ {in, not in}
Γ⊢E1 op E2:boolΓ⊢E1:string Γ⊢E2:T T∈{dictof,structof} Γ⊢E1 op E2:boolΓ⊢E1:T Γ⊢E2:listof(S),T⊑S Expr op ∈ {is, is not}
Γ⊢E1 op E2:boolΓ⊢E1:T Γ⊢E2:T IF Expr
Expr If
Γ⊢if E1 then E2 else E3:TΓ⊢E1:boolean Γ⊢E2:T Γ⊢E3:T Stmt
Stmt If
Γ⊢if E1 then S1 else S2:VoidΓ⊢E1:boolean Γ⊢S1:Void Γ⊢S2:Void Stmt Assign
Γ⊢id:T1 = E:VoidΓ⊢id:T0 Γ⊢T1 Γ⊢E:T2 Type Alias
Γ⊢type id = T1:VoidΓ⊢id:T0 Γ⊢T1 Union
List Union
Γ⊢ listof(unionof(T,S))Γ⊢ listof(T) Γ⊢ listof(S) Dict Union
Γ⊢ dictof(unionof(T1,S1),unionof(T2,S2))Γ⊢ dictof(T1,T2) Γ⊢ dictof(S1,S2) Struct Union
Define two structures: structof(K1:T1,...,Kn:Tn),structof(H1:S1,...,Hm:Sn)
Define their union types:
structof(J1:U1,...,Jp:Un)=structof(K1:T1,...,Kn:Tn)⋃structof(H1:S1,...,Hm:Sn) Example
structof() ⋃ structof(H1:T1,...,Hm:Tn)=structof(H1:T1,...,Hm:Tn) structof(K1:T1,...,Kn:Tn) ⋃ structof(H1:S1,...,Hm:Sn)=structof(K1:T1)::(structof(K2:T2,...,Kn:Tn) ⋃ structof(H1:S1,...,Hm:Sn)) where "::" denotes the operation of adding a dual to a structure, which is defined as follows:
structof(K1:T1)::structof()=structof(K1:T1) structof(K1:T1)::structof(K1:T1′,...,Kn:Tn)=structof(K1:union_op(T1,T1′),...,Kn:Tn) structof(K1:T1)::structof(K2:T2,...,Kn:Tn)=structof(K2:T2)::structof(K1:T1)::structof(K3:T3,...,Kn:Tn) Based on this, the union of two structures is defined as:
Γ⊢structof(J1:U1,...,Jp:Un))Γ⊢structof(K1:T1,...,Kn:Tn) Γ⊢structof(H1:S1,...,Hm:Sn) structof(J1:U1,...,Jp:Un)=structof(K1:T1,...,Kn:Tn)⋃structof(H1:S1,...,Hm:Sn) where union_op(T1,T2) denotes different types of judgment operations for the same Ki.
- When T1 and T2 have the partial order relation. If T1⊑T2, return T2, otherwise return T1, which is the minimum upper bound
- When T1 and T2 have no partial order relationship, there are three optional processing logic:
- Structure union failed, return a type error.
- Return the type of the latter T2.
- Return the type unionof(T1,T2).
Here, we need to choose the appropriate processing method according to the actual needs.
Structure inheritance can be regarded as a special union. The overall logic is similar to that of union, but in union_op(T1,T2) for the same Ki, the different types of judgment operations are as follows:
- When T1 and T2 have the partial order relation and T1⊑T2, return T1, that is, only if T1 is the lower bound of T2, the lower bound of T1 shall prevail.
- Otherwise, a type error is returned.
Through such inheritance design, we can achieve hierarchical, bottom-up and layer-by-layer contraction of type definition.
Operation
KCL supports operations on structure attributes in the form of popE. That is, for the given structure A:structof(K1:T1,...,Kn:Tn), the path p in the structure is specified with the value of E (such as union, assign, insert, etc.).
Define the following update operations:
A{p op e}:{K1:T1,...,Kn:Tn}∪{p:T}Γ⊢A:structof(K1:T1,...,Kn:Tn) Γ⊢p∈(K1,...,Kn) Γ⊢e:T k=k1,...,k=kn That is to say, the operation on the path p is essentially a union of two structures. The rules for the same name attribute type union depend on the situation. For example, the path p is an identifier p=k1 that can be used as a field name k1, and the field name in structure A is also k1, its type is T1, and the type of the expression e is also T1, then
A{p op e}:{K1:T1,...,Kn:Tn}Γ⊢A:structof(K1:T1,...,Kn:Tn) Γ⊢p=K1 Γ⊢e:T1 k=k1,...,k=kn Note:
- The type T1 of the expression e have the same type with the original attribute of the same name K1. It can be relaxed appropriately according to the actual situation, such as the type of e ⊑T1 is enough.
- For the operation of nested multi-layer structures, the above rules can be used recursively.
Type Partial Order
Basic Types
Type T⊑Type Any Type Nothing⊑Type T Type Nothing⊑Type Bool⊑Type Any Type Nothing⊑Type Int⊑Type Any Type Nothing⊑Type Float⊑Type Any Type Int⊑Type Float Type Nothing⊑Type String⊑Type Any Type Nothing⊑Type Literal⊑Type Any Type Nothing⊑Type List⊑Type Any Type Nothing⊑Type Dict⊑Type Any Type Nothing⊑Type Struct⊑Type Any Type Nothing⊑Type None⊑Type Any Type Nothing⊑Type Void⊑Type Any Type Nothing⊑Type Any Literal Type
Type Literal(Bool)⊑Type Bool Type Literal(Int)⊑Type Int Type Literal(Float)⊑Type Float Type Literal(String)⊑Type String Union Type
Type X⊑Type Union(X,Y) Introspect
Type X⊑Type X 示例
Type Bool⊑Type Bool Type Int⊑Type Int Type Float⊑Type Float Type String⊑Type String Type List⊑Type List Type Dict⊑Type Dict Type Struct⊑Type Struct Type Nothing⊑Type Nothing Type Any⊑Type Any Type Union(TypeInt,TypeBool)⊑Type Union(TypeInt,TypeBool) Transmit
Type X⊑Type Z if Type X⊑Type Y and Type Y⊑ Type Z Contained
Type List(T1)⊑Type List(T2) if T1⊑T2 Type Dict(Tk1,Tv1)⊑Type Dict(Tk2,Tv2) if Tk1⊑Tk2 and Tv1⊑Tv1 Type Structure(K1:Ta1,K2:Ta2,...,Kn:Tan)⊑Type Structure(K1:Tb1,K2:Tb2,...,Kn:Tbn) if Ta1⊑Tb1 and Ta2⊑Tb2 and ... and Tan⊑Tbn Inheritance
Type Struct A⊑Type Struct B if A inherits B None
Type None⊑Type X,X∈/{Type Nothing, Type Void} Undefined
Type Undefined⊑Type X,X∈/{Type Nothing, Type Void} Equality
Type Union(X,Y)==Type Union(Y,X) Example
Type Union(Int,Bool)==Type Union(Bool,Int) Type Union(Union(X,Y),Z)==Type Union(X,Union(Y,Z)) Example
Type Union(Union(Int,String),Bool)==Type Union(Int,Union(String,Bool)) Type Union(X,X)==Type X Example
Type Union(Int,Int)==Type Int Partial order derivation
Type Union(X,Y)==Type Y if X⊑Y Example
Assume that Struct A inherits Struct B
Type Union(A,B)==Type B Idempotency is a special case of partial order reflexivity
List
Type List(X)==Type List(Y) if X==Y Dict
Type Dict(Tk,Tv)==Type Dict(Sk,Sv) if Tk==Sk and Tv==Sv Struct
Type Struct(K1:T1,K2:T2,...,Kn:Tn)==Type Struct(K1:S1,K2:S2,...,Kn:Sn) if T1==S1 and ... and Tn==Sn Partial Order Checking
Type X==Type Y if Type X⊑Type Y and Type Y⊑ Type X Basic Methods
sup(t1: T, t2: T) -> T
: Calculate the minimum upper bound of two types t1
and t2
according to the type partial order. The union type needs to be created dynamically.typeEqual(t1: T, t2: T) -> bool
: Compare whether the two types t1
and t2
are equal.typeToString(t: T) -> string
: Resolve and convert the type to the corresponding string type recursively from top to bottom.
Sup Function
- Type parameters, condition types and other characteristics are not considered temporarily.
- Use an ordered collection to store all types of
UnionType
. - Use a global map to store all generated union types according to the name of
UnionType
. - Calculate the inclusion relationship between types according to the partial order relationship.
func Sup(types: T[]) -> T {
typeOf(types, removeSubTypes=true)
}
func typeOf(types: T[], removeSubTypes: bool = false) -> T {
assert isNotNullOrEmpty(types)
typeSet: Set[T] = {}
addTypesToTypeSet(typeSet, types)
if removeSubTypes {
removeSubTypes(typeSet)
}
if len(typeSet) == 1 {
return typeSet[0]
}
id := getIdentifierFromTypeSet(typeSet)
unionType := globalUnionTypeMap.get(id)
if !unionType {
unionType = createUnionType(typeSet)
globalUnionTypeMap.set(id, unionType)
}
return unionType
}
func addTypesToTypeSet(typeSet: Set[T], types: T[]) -> void {
for type in types {
addTypeToTypeSet(typeSet, type)
}
}
func addTypeToTypeSet(typeSet: Set[T], type: T) -> void {
if isUnion(type) {
return addTypesToTypeSet(typeSet, toUnionOf(type).types)
}
if !isVoid(type) {
typeSet.add(type)
}
}
func removeSubTypes(types: Set[T]) -> void {
for source in types {
for target in types {
if !typeEqual(source, target) {
if (isPartialOrderRelatedTo(source, target)) {
types.remove(source)
}
}
}
}
}
func isPartialOrderRelatedTo(source: T, target: T) -> bool {
assert isNotNullOrEmpty(source)
assert isNotNullOrEmpty(target)
if isNoneOrUndefined(source) and !isNothing(target) and !isVoid(target) {
return true
}
if isAny(target) {
return true
}
if typeEqual(source, target) {
return true
}
if isUnion(target) and source in target.types {
return true
}
if (isStringLiteral(source) and isString(target)) or \
(isBooleanLiteral(source) and isBool(target)) or \
(isIntLiteral(source) and isInt(target)) or \
(isFloatLiteral(source) and isFloat(target)) {
return true
}
if isInt(source) and isFloat(target) {
return true
}
if isList(source) and isList(target) {
return isPartialOrderRelatedTo(toListOf(source).eleType, toListOf(target).eleType
}
if isDict(source) and isDict(target) {
return isPartialOrderRelatedTo(toDictOf(source).keyType, toDictOf(target).keyType) and isPartialOrderRelatedTo(toDictOf(source).valueType, toDictOf(target).valueType)
}
if isStruct(source) and isStruct(target) {
if isTypeDerivedFrom(source, target) {
return true
}
if len(target.keys) == 0 {
return true
}
if any([key Not in source.keys for key in target.keys]) {
return false
}
for key, sourceType in (source.keys, source.types) {
targetType = getKeyType(target, key) ? getKeyType(target, key) : anyTypeOf()
if !isPartialOrderRelatedTo(sourceType, targetType) {
return false
}
}
return true
}
return false
}
Type Checking
Checker
The type checker traverses the syntax tree from top to bottom through syntax-directed translation, and determines whether the program structure is a well-typed program according to context-sensitive training rules.
The type checker depends on type rules, and the information of type environment Γ is recorded in the symbol table. Use abstract syntax for type expressions, such as listof (T)
. When the type check fails, a type mismatch error is generated, and the error message is generated according to the syntax context.
Basic Methods
isUpperBound(t1, t2): supUnify(t1, t2) == t2
supUnify(t1, t2):
- For the foundation type,
sup(t1, t2)
is calculated according to the partial order relationship - For list, dict, Struct, recursively
supUnify
the types of elements - When there is no partial order relationship, return
Nothing
Checking Logic
Operand Expr
D→id:T
env.addtype(id.entry, T.type)
T→boolean
T→integer
T→float
T→string
T→c, c∈{boolean,integer,float,string}
T→None
T→Undefined
T→ [T1]
T.type = listof (T1.type)
T→{T1:T2}
T.type = dictof (T1.type: T2.type)
T→{N1:T1,N2:T2,...,Nn:Tn}
T.type = structof (N1: T1.type, N2: T2.type, ..., Nn: Tn.type)
E→id
E.type = env.lookup(id.entry)
E→[E1,E2,...,En]
func listExpr(E) {
supe = sup([e.type for e in E]])
E.type = listof(type)
}
E→[E1 for E2 in E3 if E4]
func listComp(E) {
if !typeEqual(E4.type, boolean) {
raise type_error
}
if !isUpperBound(listof(Any), E3.type) {
raise type_error(E)
}
if !isUpperBound(E3.type, E2.type) {
raise type_error(E)
}
E.type = listof(E1.type)
}
E→{Ek1:Ev1,...,Ekn:Evn}
func dictExpr(E) {
supk := sup([e.type for e in E.keys()]])
supv := sup([e.type for e in E.values()]])
E.type = dictof(supk, supv)
}
E→{E1:E2 for (E3,E4) in E5 if E6}
func dictComp(E) {
if !typeEqual(E6.type, boolean) {
raise type_error(E)
}
if !isUpperBound(dictof(Any, Any), E5.type) {
raise type_error(E)
}
if !isUpperBound(E5.type, dictof(E3.type, E4.type)) {
raise type_error(E)
}
E.type = dictof(E1.type, E2.type)
}
E→{Ek1:Ev1,...,Ekn:Evn}
func dictExpr(E) {
supk := sup(Ek1, ..., Ekn)
supv = sup(Ev1, ..., Evn)
E.type = dictof(supk, supv)
}
E→{N1=E1,...,Nn=En}
func structExpr(E) {
Struct = structof()
for n, e in E {
Struct.add(n, e.type)
}
E.type = Struct
}
Primary Expr
E→E1[E2]
func sliceSuffix(E) {
if !isUpperBound(listof(Any), E.E1.type) {
raise type_error(E)
}
if typeEqual(E.E2.type, integer) {
raise type_error(E)
}
E.type = E.E1.type.eleType
}
E→E1(E2)
func callSuffix(E) {
if !typeEqual(E.E1.type, func) {
raise type_error(E)
}
if !isUpperBound(listof(E.E1.arguType), E.E2.type) {
raise type_error(E)
}
E.type = E.E1.returnType
}
Unary Expr
According to the reasoning rules of each binocular operator, take +
as an example.
E→+E1
func Plus(E) {
if !typeEqual(E.E1.type, [integer, float]) {
raise type_error(E)
}
E.type = E.E1.type
}
Binary Expr
According to the reasoning rules of each binocular operator, take %
as an example.
E→E1
func Mod(E) {
if !(typeEqual(E.E1.type, [integer, float]) && typeEqual(E.E2.type, [integer, float])) {
raise type_error(E)
}
E.type = E.E1.type
}
IF Binary Expr
E→ifE1 then E2else E3
func ifExpr(E) {
if !typeEqual(E.type, boolean) {
raise type_error(E)
}
if !typeEqual(E_2.type, E_3.type) {
raise type_error(E)
}
E.type = E_2.type
}
Stmt
S→if E then S1 else S2
func ifStmt(S) {
if !typeEqual(S.E.type, boolean) {
raise type_error(E)
}
if !typeEqual(S.S1.type, S.S2.type) {
raise type_error(E)
}
S.type = S.S1.type
}
S→id:T=E
S→id=TE
func assignStmt(S) {
tpe := env.lookup(id.entry)
if tpe != nil && tpe != S.T {
raise type_error(E)
}
if isUpperBound(tpe, E.type) {
raise type_error(E)
}
env.addtype(id.entry, T.type)
}
Type Conversion
Basic Definition
Through syntax-directed translation, the value types involved in the operation are automatically converted according to the operator characteristics.
Conversion Rules
E→E1 op E2,,op∈{+,−,∗,/,%,∗∗,//}
func binOp(E) {
if E.E1.type == integer and E.E2.type == integer {
E.type = integer
} else if E.E1.type == integer and E.E2.type == float {
E.type = float
} else if E.E1.type == float and E.E2.type == integer {
E.type = float
} else if E.E1.type == float and E.E2.type == float {
E.type = float
}
}
Type Inference
Basic Definition
- Type rule derivation and type reconstruction in case of incomplete type information
- Derive and reconstruct the data structure types in the program from the bottom up, such as basic type, e.g., list, dict and struct types.
Basic Methods
typeOf(expr, subst)
: The input is the expression and substitution rule set, and the type of expr and the new substitution rule set are returned.unifier(t1, t2, subst, expr)
: Try substitution with t1=t2
. If the substitution is successful (no occurrence and no conflict), add t1=t2
to the subst and return the subst. Otherwise, an error has occurred or there is a conflict.
Inferential Logic
E→id=E1
func assignExpr(E, subst) {
return unifier(E.id.type, E.E_1.type, subst, E)
}
unifier(t1,t2,subst,expr)→subst
func unifier(t1, t2, subst, expr) {
t1 = applySubstToTypeEquation(t1, subst)
t2 = applySubstToTypeEquation(t2, subst)
if t1 == t2 {
return subst
}
if isTypeVar(t1) {
if isNoOccur(t1, t2) {
addTypeEquationToSubst(subst, t1, t2)
return subst
} else {
raise occurrence_violation_error(t1, t2, expr)
}
}
if isTypeVar(t2) {
if isNoOccur(t2, t1) {
addTypeEquationToSubst(subst, t2, t1)
return subst
} else {
raise occurrence_violation_error(t2, t1, expr)
}
}
if isList(t1) and isList(t2) {
return unifier(toListOf(t1).eleType, toListOf(t2).eleType, subst, expr)
}
if isDict(t1) and isDict(t2) {
dict1of := toDictOf(t1)
dict2of := toDictOf(t2)
subst = unifier(dict1of.keyType, dict2of.keyType, subst, expr)
subst = unifier(dict1of.valueType, dict2of.valueType, subst, expr)
return subst
}
if isStruct(t1) and isStruct(t2) {
Struct1of := tostructof(t1)
Struct2of := tostructof(t2)
for key, _ in Struct1of {
subst = unifier(t1[key].type, t2[key].type, subst, expr)
}
return subst
}
raise unification_error(t1, t2, expr)
}
func applySubstToTypeEquation(t, subst) {
// walks through the type t, replacing each type variable by its binding in the substitution
σ. If a variable is Not bound in the substitution, then it is left unchanged.
if isBasicType(t) {
return t
}
if isList(t) {
return listOf(applySubstToTypeEquation(toListOf(t).eleType, subst))
}
if isDict(t) {
dictof := toDictOf(t)
kT := applySubstToTypeEquation(dictof.keyType, subst)
vT := applySubstToTypeEquation(dictof.valueType, subst)
return dictOf(kT, vT)
}
if isStruct(t) {
structof := tostructof(t)
s := structof()
for key, type in Struct1of {
kT := applySubstToTypeEquation(type, subst)
s.add(key, kT)
}
return s
}
if hasTypeVar(t) {
for tvar in t.vars {
if tvar in subst {
*tvar = subst[tvar]
}
}
}
return t
}
func addTypeEquationToSubst(subst, tvar, t) {
// takes the substitution σ and adds the equation tv = t to it
for _, t in subst {
for tvar in t.vars {
tmp := applyOneSubst(tsvar, tvar, t)
*tvar = tmp
}
}
subst.add(tvar, t)
}
func applyOneSubst(t0, tvar, t1) {
// substituting t1 for every occurrence of tv in t0.
if isBasicType(t0) {
return t0
}
if isList(t0) {
return listOf(applyOneSubst(toListOf(t).eleType, tvar, t1))
}
if isDict(t0) {
dictof := toDictOf(t)
kT := applyOneSubst(dictof.keyType, tvar, t1)
vT := applyOneSubst(dictof.valueType, tvar, t1)
return dictOf(kT, vT)
}
if isStruct(t0) {
structof := tostructof(t)
s := structof()
for key, type in Struct1of {
kT := applyOneSubst(type, tvar, t1)
s.add(key, kT)
}
return s
}
if t0 == tvar {
return t1
}
return t0
}
func isNoOccur(tvar, t) {
// No variable bound in the substitution occurs in any of the right-hand sides of the substitution.
if isBasicType(t) {
return true
}
if isList(t) {
return isNoOccur(tvar, toListOf(t).eleType)
}
if isDict(t) {
dictof := toDictOf(t)
return isNoOccur(tvar, dictof.keyType) and isNoOccur(tvar, dictof.valueType)
}
if isStruct(t) {
structof := tostructof(t)
noOccur := true
for _, type in structof {
noOccur = noOccur and isNoOccur(tvar, type)
}
return noOccur
}
return tvar != t
}
Example
Normal Inference
T : {
a = 1
b = "2"
c = a * 2
d = {
d0 = [a, c]
}
}
x: T = {
a = 10
}
Occurrence Violation Error
Type Unification Error
T : {
a = 1
}
T : {
a = "1"
}
Reference