This checklist containts only major changes and updates, for minor changes and updates, please refer to the commit history.
-
13/03/2024:
- Finding common types for multiple datatypes
typeinference.ts:findCompatibleTypes
- Casting generic types to specific types, such as getting an interface from a reference or an interface from a join types, using
DataType.is(InterfaceType)
, andDataType.to(InterfaceType)
, these functions are implemented internally to return and perform the appropriate casting. - Basic implementation of cloning for declared functions, etc.
- Finding common types for multiple datatypes
-
13/03/2024:
- Infer functions for some expressions
- MetaTypes (also known as RawTypes in the old compiler), which holds enums, classes, interfaces, variants and variant consturctors
- New method
allowNullable
, which indicates that a datatype is allowed to be wrapped within aNullable
class. For example Classes, interfaces, struct, variants can be nullables. - New method
Expression.checkHint
used to compare the hint with the inferred type of the expression, since the check is very repetitive.
-
14/03/2024:
- Removed auto returning
inferredType
when set, from within expression, when we re-infer function return expression, the hint is not set and type checks are not performed since it auto-returns first thing during inference. One solution is to not infer return statements expressions, gotta investigate!
- Removed auto returning
-
17/03/2024:
Context.addSymbol
now sets theparentContext
of the symbol added, if a symbol is imported,Context.addExternalSymbol
is used instead, which does not set theparentContext
of the symbol added.ReferenceType
now requires attributeusageContext
, which points the context in which the reference is used, and Not where the reference is declared!- Since generic class methods do not support overloading, the method
ClassType.getGenericMethodByName(name: string)
has been introduced and used inFunctionCallExpression
to get the generic method to be called.
-
18/03/2024:
- Variant constructors are matched based not only on name and argument but at their position within the parent.
-
20/03/2024:
- Added
getGenericParameters(ctx: Context, originalType: DataType, declaredGenerics: {[key: string]: GenericType}): {[key: string]: DataType}
which returns the list of generic types with a compound type, this is used withinClassType.getMethodBySignature
to create concrete method from generic method and a given signature. It makes sure that redudant generic usages are consistent and throws error on failure
- Added
-
28/03/2024:
- Removed processes concept from the language.
- Added
locks
andpromises
as builtin types. - Class checking is now based on location, until a better solution comes up
getMethodBySignature
now also takes type arguments, so it can infer generics when not present, or just clone the methods when they are present.
-
04/03/2024:
- Context has
uuid
and it assigns auid
to all symbols it owns. ClassMethod
,LambdaExpression
andDeclaredFunction
now have an attributecodeGenProps: FunctionCodegenProps
, which contains all symbols (locals, arguments and upvalues) of that function to be used for code gen. This field is filled when theElement
(terminal expression) is being resolved throughlookupScope
, and this new classFunctionCodegenProps
is used to report unused arguments in the function.- Added a field
wasInferred
toDeclaredFunction
instances to avoid inferring non-generic function declaration multiple times.
- Context has
-
04/04/2024:
- During
FunctionCallExpression
inference, if the LHS is an element, the arguments are inferred asnull
hint and the fieldinferredArgumentsTypes: DataType[] | undefined = undefined
is set for the element. If the element is a generic function call, and has no type arguments, generics are extracted from theinferredArgumentsTypes
field (similar to class methods). - ~~ (old) Fix cloning of variable declarations and function declarations, also cloning scopes doesn't copy the symbols anymore. It only copies variables and functions since these are static and already set by the parser.~~
inferredArgumentsTypes
is only set inFunctionCallExpression
only when lhs is an element and has no generic parameters. Next is to only set it if the LHS is generic and has no type arguments.- Now we infer the arguments of generics only if the function being called is generic and has no type arguments, otherwise we use the type arguments provided.
- Lambda expressions now have a symbol attached to them to be registered in global context
- During
-
13/04/2024:
- Originally, variables and functions are added to their parent context during parsing. This behavior has been changed, they are now added to the context when their respective declaration statement is being resolved. This makes context cloning simpler, since we init the cloned context with empty symbols, the symbols will be added when the declaration is resolved when resolving the parenting cloned statement/expr.
-
29/07/2024:
- Added Tuple data type.
-
5/08/2024:
- Added destructuring assignment for tuples, structs and arrays.
-
6/08/2024:
- Added
external
attribute to symbols, to mark if a symbol is external or not. Was a bug that resulted in external symbols added to the global context and having their IR generated twice (one from original and one from context that imported it). - Added
getAllMethods
andbuildAllMethods
toClassType
to build all methods for a class, including the implemenation of all generic methods static and not.
- Added
-
8/08/2024:
- Added
DoExpression
- Added
-
10/08/2024:
- Refactored bytecode for structs, a global id for every field is generated, covering all combinations, this global id is used to access the field in the struct, using an additional offset byte.
- Refactored bytecode for classes and interfaces. VM now only supports classes. Class methods are processed the same way as structs now, each method has a global id.
- class to interface now is direct, interface to interface is now done through new
i_has_m
bytecode instruction, which checks if the class has the method.
-
12/08/2024:
- Isolated static methods, so static methods are stored in the base class type, not classes that are concrete implementations of that class.
- Static methods now support generics.
-
13/08/2024:
- This is a reminder for me to differentiate between anonymous types and named types! this needs to be modeled properly, to generate proper errors and start working on types layouts in the bytecode.
-
17/08/2024:
- This expression now added recusively upwards, as an upvalue (obviously not added to the method itself, but any function/lambda within the class method)
- Closures are finally here!
-
Allow class attributes (both static and not static) to be immutable, and can only be set from within the constructor.
-
Address the issue of non-inferred expressions suchas expressions as arguments to method call.
dt.callMe({"user", 20})
In such case, the compiler will not infer the unnamed struct construction
{"user", 20}
with the method argument, due to method overload resolution. -
Pad structs in the VM, for faster CPU access
- Implement Nullish coalescing operator as a binary operator, will require additional parameter to
expresion.infer
so when we encounter nullable member access we can accept it knowing that there is a fallback value:a?.b ?? 0
. - Add Short-circuiting logical operators and nullish coalescing operator (codegen)
- Add language level support for threads
- Infer generic method call without exilicitly specifying the generic types (from within
FunctionCallExpression
) -
Add support Shadow Classes (requires VM integration too) - Bytecode generation
let l1: lock<u32> = new lock(0)
fn f1() -> i32 {
let z: i32 = 1
return z
}
let thread1 = spawn f1()
let x: u64 = (await thread1) as u32
results in:
tests/test18/test.tc:17:30:Cannot cast u32 to u64: Type mismatch, expected u32, got u64
let x: u64 = (await thread1) as u32
-> Casting with target u32 and a hint of u64.
fn changeme(mut x: u32){
x = 1
}
let y: u32 = 0
changeme(y) // doesn't work! compiler needs to get the value back and update
class A {
fn f1<T>(x: T){}
fn f1(x: u32){}
}
and call new A().f1(0)
, should not have ambiguity, an error should be thrown.
If we somehow convert class methods into closures, we can allow for class methods to be used as fuctions, first class citizens.
class x {
let x = 1
fn addSomething(y: u32) {
x += y
}
}
We transform addSomething
into a closure, requiring this
as an upvalue, meaning compiled code is
something as follows:
class x {
fn __compiler_init__() {
this = new X()
x.addSomething = fn(y: u32) {
this.x += y
}
return x
}
}
Hence making x.addSomething(1)
a method that is bound to the instance of the class.
Even better, if we can add syntatic sugar to allow such things, because closures comes with overhead.
class x {
let x = 1
@closure
fn addSomething(y: u32) {
}
}
### Pattern matching over custom types:
bytecode generation for pattern matching uses primitive types such as array slicing in case
`[x, y, ...rest]`
Inorder to allow matching over custom types, we need to add custom overloads for pattern matching,
for instance for array matching we need the index access override and a custom slice implementation.
Maybe even a built-in range class? like python x[0:3] -> x[Range(0, 3)] by overloading the index access operator.
Lots of potential work here, but also low priority.
### Improve performance, by a lot!
if we can create "templates" for types, we can then use local index instead of global offset for structs, interfaces and classes.
Hence we can avoid searching for index within the globalIndex table.
For each **defined** type, we add it to the template segment, each object will then be created
based on that template (it has predefined index for its fields), and we can then use that index.
Each struct/class/interface/variant will need to have an ID referencing the template ID used to create it.
When generating bytecode, we will need instruction to fetch data from local index instead of global index.