aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGinger Bill <bill@gingerbill.org>2017-07-13 22:35:00 +0100
committerGinger Bill <bill@gingerbill.org>2017-07-13 22:35:00 +0100
commit1c5ddd65b4f66c4d37bd9da0facb229bbcea22eb (patch)
tree78078fbb2cf23b31689bb6842ba30e487f199447
parentb8697fb4ed34d0da0fa0888b57e6edcc37a0ce81 (diff)
Rudimentary support for parametric polymorphic types
-rw-r--r--code/demo.odin445
-rw-r--r--code/demo_backup.odin430
-rw-r--r--src/check_decl.cpp2
-rw-r--r--src/check_expr.cpp525
-rw-r--r--src/check_stmt.cpp251
-rw-r--r--src/checker.cpp3
-rw-r--r--src/ir.cpp20
-rw-r--r--src/ir_print.cpp2
-rw-r--r--src/parser.cpp60
-rw-r--r--src/types.cpp16
10 files changed, 1114 insertions, 640 deletions
diff --git a/code/demo.odin b/code/demo.odin
index 8eeaeb357..6e0ae84fa 100644
--- a/code/demo.odin
+++ b/code/demo.odin
@@ -1,430 +1,29 @@
-import (
- "fmt.odin";
- "atomics.odin";
- "bits.odin";
- "decimal.odin";
- "hash.odin";
- "math.odin";
- "mem.odin";
- "opengl.odin";
- "os.odin";
- "raw.odin";
- "strconv.odin";
- "strings.odin";
- "sync.odin";
- "sort.odin";
- "types.odin";
- "utf8.odin";
- "utf16.odin";
-/*
-*/
-)
-
-
-general_stuff :: proc() {
- // Complex numbers
- a := 3 + 4i;
- b: complex64 = 3 + 4i;
- c: complex128 = 3 + 4i;
- d := complex(2, 3);
-
- e := a / conj(a);
- fmt.println("(3+4i)/(3-4i) =", e);
- fmt.println(real(e), "+", imag(e), "i");
-
-
- // C-style variadic procedures
- foreign __llvm_core {
- // The variadic part allows for extra type checking too which C does not provide
- c_printf :: proc(fmt: ^u8, #c_vararg args: ...any) -> i32 #link_name "printf" ---;
- }
- str := "%d\n\x00";
- // c_printf(&str[0], i32(789456123));
-
-
- Foo :: struct {
- x: int;
- y: f32;
- z: string;
- }
- foo := Foo{123, 0.513, "A string"};
- x, y, z := expand_to_tuple(foo);
- fmt.println(x, y, z);
- compile_assert(type_of(x) == int);
- compile_assert(type_of(y) == f32);
- compile_assert(type_of(z) == string);
-
-
- // By default, all variables are zeroed
- // This can be overridden with the "uninitialized value"
- // This is similar to `nil` but applied to everything
- undef_int: int = ---;
-
-
- // Context system is now implemented using Implicit Parameter Passing (IPP)
- // The previous implementation was Thread Local Storage (TLS)
- // IPP has the advantage that it works on systems without TLS and that you can
- // link the context to the stack frame and thus look at previous contexts
- //
- // It does mean that a pointer is implicitly passed procedures with the default
- // Odin calling convention (#cc_odin)
- // This can be overridden with something like #cc_contextless or #cc_c if performance
- // is worried about
-
-}
-
-foreign_blocks :: proc() {
- // See sys/windows.odin
-}
-
-default_arguments :: proc() {
- hello :: proc(a: int = 9, b: int = 9) do fmt.printf("a is %d; b is %d\n", a, b);
- fmt.println("\nTesting default arguments:");
- hello(1, 2);
- hello(1);
- hello();
-}
-
-named_arguments :: proc() {
- Colour :: enum {
- Red,
- Orange,
- Yellow,
- Green,
- Blue,
- Octarine,
- };
- using Colour;
-
- make_character :: proc(name, catch_phrase: string, favourite_colour, least_favourite_colour: Colour) {
- fmt.println();
- fmt.printf("My name is %v and I like %v. %v\n", name, favourite_colour, catch_phrase);
- }
-
- make_character("Frank", "¡Ay, caramba!", Blue, Green);
-
-
- // As the procedures have more and more parameters, it is very easy
- // to get many of the arguments in the wrong order especialy if the
- // types are the same
- make_character("¡Ay, caramba!", "Frank", Green, Blue);
-
- // Named arguments help to disambiguate this problem
- make_character(catch_phrase = "¡Ay, caramba!", name = "Frank",
- least_favourite_colour = Green, favourite_colour = Blue);
-
-
- // The named arguments can be specifed in any order.
- make_character(favourite_colour = Octarine, catch_phrase = "U wot m8!",
- least_favourite_colour = Green, name = "Dennis");
-
-
- // NOTE: You cannot mix named arguments with normal values
- /*
- make_character("Dennis",
- favourite_colour = Octarine, catch_phrase = "U wot m8!",
- least_favourite_colour = Green);
- */
-
-
- // Named arguments can also aid with default arguments
- numerous_things :: proc(s: string, a := 1, b := 2, c := 3.14,
- d := "The Best String!", e := false, f := 10.3/3.1, g := false) {
- g_str := g ? "true" : "false";
- fmt.printf("How many?! %s: %v\n", s, g_str);
- }
-
- numerous_things("First");
- numerous_things(s = "Second", g = true);
-
-
- // Default values can be placed anywhere, not just at the end like in other languages
- weird :: proc(pre: string, mid: int = 0, post: string) {
- fmt.println(pre, mid, post);
- }
-
- weird("How many things", 42, "huh?");
- weird(pre = "Prefix", post = "Pat");
-
-}
-
-
-default_return_values :: proc() {
- foo :: proc(x: int) -> (first: string = "Hellope", second := "world!") {
- match x {
- case 0: return;
- case 1: return "Goodbye";
- case 2: return "Goodbye", "cruel world...";
- case 3: return second = "cruel world...", first = "Goodbye";
+import "fmt.odin";
+
+Vector :: struct(N: int, T: type) {
+ using _: raw_union {
+ using e: [N]T;
+ when 0 < N && N <= 4 {
+ using v: struct {
+ when N >= 1 do x: T;
+ when N >= 2 do y: T;
+ when N >= 3 do z: T;
+ when N >= 4 do w: T;
+ };
}
-
- return second = "my old friend.";
- }
-
- fmt.printf("%s %s\n", foo(0));
- fmt.printf("%s %s\n", foo(1));
- fmt.printf("%s %s\n", foo(2));
- fmt.printf("%s %s\n", foo(3));
- fmt.printf("%s %s\n", foo(4));
- fmt.println();
-
-
- // A more "real" example
- Error :: enum {
- None,
- WhyTheNumberThree,
- TenIsTooBig,
};
-
- Entity :: struct {
- name: string;
- id: u32;
- }
-
- some_thing :: proc(input: int) -> (result: ^Entity = nil, err := Error.None) {
- match {
- case input == 3: return err = Error.WhyTheNumberThree;
- case input >= 10: return err = Error.TenIsTooBig;
- }
-
- e := new(Entity);
- e.id = u32(input);
-
- return result = e;
- }
-}
-
-call_location :: proc() {
- amazing :: proc(n: int, using loc := #caller_location) {
- fmt.printf("%s(%d:%d) just asked to do something amazing.\n",
- fully_pathed_filename, line, column);
- fmt.printf("Normal -> %d\n", n);
- fmt.printf("Amazing -> %d\n", n+1);
- fmt.println();
- }
-
- loc := #location(main);
- fmt.println("`main` is located at", loc);
-
- fmt.println("This line is located at", #location());
- fmt.println();
-
- amazing(3);
- amazing(4, #location(call_location));
-
- // See _preload.odin for the implementations of `assert` and `panic`
-
}
-
-explicit_parametric_polymorphic_procedures :: proc() {
- // This is how `new` is actually implemented, see _preload.odin
- alloc_type :: proc(T: type) -> ^T do return cast(^T)alloc(size_of(T), align_of(T));
-
- int_ptr := alloc_type(int);
- defer free(int_ptr);
- int_ptr^ = 137;
- fmt.println(int_ptr, int_ptr^);
-
- // Named arguments work too!
- another_ptr := alloc_type(T = f32);
- defer free(another_ptr);
-
-
- add :: proc(T: type, args: ...T) -> T {
- res: T;
- for arg in args do res += arg;
- return res;
- }
-
- fmt.println("add =", add(int, 1, 2, 3, 4, 5, 6));
-
- swap :: proc(T: type, a, b: ^T) {
- tmp := a^;
- a^ = b^;
- b^ = tmp;
- }
-
- a, b: int = 3, 4;
- fmt.println("Pre-swap:", a, b);
- swap(int, &a, &b);
- fmt.println("Post-swap:", a, b);
- a, b = b, a; // Or use this syntax for this silly example case
-
-
- Vector2 :: struct {x, y: f32;};
- {
- // A more complicated example using subtyping
- // Something like this could be used in a game
-
- Entity :: struct {
- using position: Vector2;
- flags: u64;
- id: u64;
- derived: any;
- }
-
- Rock :: struct {
- using entity: Entity;
- heavy: bool;
- }
- Door :: struct {
- using entity: Entity;
- open: bool;
- }
- Monster :: struct {
- using entity: Entity;
- is_robot: bool;
- is_zombie: bool;
- }
-
- new_entity :: proc(T: type, x, y: f32) -> ^T {
- result := new(T);
- result.derived = result^;
- result.x = x;
- result.y = y;
-
- return result;
- }
-
- entities: [dynamic]^Entity;
-
- rock := new_entity(Rock, 3, 5);
-
- // Named arguments work too!
- door := new_entity(T = Door, x = 3, y = 6);
-
- // And named arguments can be any order
- monster := new_entity(
- y = 1,
- x = 2,
- T = Monster,
- );
-
- append(&entities, rock, door, monster);
-
- fmt.println("Subtyping");
- for entity in entities {
- match e in entity.derived {
- case Rock: fmt.println("Rock", e.x, e.y);
- case Door: fmt.println("Door", e.x, e.y);
- case Monster: fmt.println("Monster", e.x, e.y);
- }
- }
- }
- {
- Entity :: struct {
- using position: Vector2;
- flags: u64;
- id: u64;
- variant: union { Rock, Door, Monster };
- }
-
- Rock :: struct {
- using entity: ^Entity;
- heavy: bool;
- }
- Door :: struct {
- using entity: ^Entity;
- open: bool;
- }
- Monster :: struct {
- using entity: ^Entity;
- is_robot: bool;
- is_zombie: bool;
- }
-
- new_entity :: proc(T: type, x, y: f32) -> ^T {
- result := new(Entity);
- result.variant = T{entity = result};
- result.x = x;
- result.y = y;
-
- return cast(^T)&result.variant;
- }
-
- entities: [dynamic]^Entity;
-
- rock := new_entity(Rock, 3, 5);
-
- // Named arguments work too!
- door := new_entity(T = Door, x = 3, y = 6);
-
- // And named arguments can be any order
- monster := new_entity(
- y = 1,
- x = 2,
- T = Monster,
- );
-
- append(&entities, rock, door, monster);
-
- fmt.println("Union");
- for entity in entities {
- match e in entity.variant {
- case Rock: fmt.println("Rock", e.x, e.y);
- case Door: fmt.println("Door", e.x, e.y);
- case Monster: fmt.println("Monster", e.x, e.y);
- }
- }
- }
-}
-
-
-implicit_polymorphic_assignment :: proc() {
- yep :: proc(p: proc(x: int)) {
- p(123);
- }
-
- frank :: proc(x: $T) do fmt.println("frank ->", x);
- tim :: proc(x, y: $T) do fmt.println("tim ->", x, y);
- yep(frank);
- // yep(tim);
-}
-
-
-
+Vector3 :: Vector(3, f32);
main :: proc() {
-/*
- foo :: proc(x: i64, y: f32) do fmt.println("#1", x, y);
- foo :: proc(x: type, y: f32) do fmt.println("#2", type_info(x), y);
- foo :: proc(x: type) do fmt.println("#3", type_info(x));
-
- f :: foo;
-
- f(y = 3785.1546, x = 123);
- f(x = int, y = 897.513);
- f(x = f32);
-
- general_stuff();
- foreign_blocks();
- default_arguments();
- named_arguments();
- default_return_values();
- call_location();
- explicit_parametric_polymorphic_procedures();
- implicit_polymorphic_assignment();
-
-
- // Command line argument(s)!
- // -opt=0,1,2,3
-*/
-/*
- program := "+ + * - /";
- accumulator := 0;
-
- for token in program {
- match token {
- case '+': accumulator += 1;
- case '-': accumulator -= 1;
- case '*': accumulator *= 2;
- case '/': accumulator /= 2;
- case: // Ignore everything else
- }
- }
-
- fmt.printf("The program \"%s\" calculates the value %d\n",
- program, accumulator);
-*/
+ v: Vector3;
+ v[0] = 1;
+ v[1] = 4;
+ v[2] = 9;
+ fmt.println(v.e);
+ v.x = 4;
+ v.y = 9;
+ v.z = 16;
+ fmt.println(v.v);
}
diff --git a/code/demo_backup.odin b/code/demo_backup.odin
new file mode 100644
index 000000000..8eeaeb357
--- /dev/null
+++ b/code/demo_backup.odin
@@ -0,0 +1,430 @@
+import (
+ "fmt.odin";
+ "atomics.odin";
+ "bits.odin";
+ "decimal.odin";
+ "hash.odin";
+ "math.odin";
+ "mem.odin";
+ "opengl.odin";
+ "os.odin";
+ "raw.odin";
+ "strconv.odin";
+ "strings.odin";
+ "sync.odin";
+ "sort.odin";
+ "types.odin";
+ "utf8.odin";
+ "utf16.odin";
+/*
+*/
+)
+
+
+general_stuff :: proc() {
+ // Complex numbers
+ a := 3 + 4i;
+ b: complex64 = 3 + 4i;
+ c: complex128 = 3 + 4i;
+ d := complex(2, 3);
+
+ e := a / conj(a);
+ fmt.println("(3+4i)/(3-4i) =", e);
+ fmt.println(real(e), "+", imag(e), "i");
+
+
+ // C-style variadic procedures
+ foreign __llvm_core {
+ // The variadic part allows for extra type checking too which C does not provide
+ c_printf :: proc(fmt: ^u8, #c_vararg args: ...any) -> i32 #link_name "printf" ---;
+ }
+ str := "%d\n\x00";
+ // c_printf(&str[0], i32(789456123));
+
+
+ Foo :: struct {
+ x: int;
+ y: f32;
+ z: string;
+ }
+ foo := Foo{123, 0.513, "A string"};
+ x, y, z := expand_to_tuple(foo);
+ fmt.println(x, y, z);
+ compile_assert(type_of(x) == int);
+ compile_assert(type_of(y) == f32);
+ compile_assert(type_of(z) == string);
+
+
+ // By default, all variables are zeroed
+ // This can be overridden with the "uninitialized value"
+ // This is similar to `nil` but applied to everything
+ undef_int: int = ---;
+
+
+ // Context system is now implemented using Implicit Parameter Passing (IPP)
+ // The previous implementation was Thread Local Storage (TLS)
+ // IPP has the advantage that it works on systems without TLS and that you can
+ // link the context to the stack frame and thus look at previous contexts
+ //
+ // It does mean that a pointer is implicitly passed procedures with the default
+ // Odin calling convention (#cc_odin)
+ // This can be overridden with something like #cc_contextless or #cc_c if performance
+ // is worried about
+
+}
+
+foreign_blocks :: proc() {
+ // See sys/windows.odin
+}
+
+default_arguments :: proc() {
+ hello :: proc(a: int = 9, b: int = 9) do fmt.printf("a is %d; b is %d\n", a, b);
+ fmt.println("\nTesting default arguments:");
+ hello(1, 2);
+ hello(1);
+ hello();
+}
+
+named_arguments :: proc() {
+ Colour :: enum {
+ Red,
+ Orange,
+ Yellow,
+ Green,
+ Blue,
+ Octarine,
+ };
+ using Colour;
+
+ make_character :: proc(name, catch_phrase: string, favourite_colour, least_favourite_colour: Colour) {
+ fmt.println();
+ fmt.printf("My name is %v and I like %v. %v\n", name, favourite_colour, catch_phrase);
+ }
+
+ make_character("Frank", "¡Ay, caramba!", Blue, Green);
+
+
+ // As the procedures have more and more parameters, it is very easy
+ // to get many of the arguments in the wrong order especialy if the
+ // types are the same
+ make_character("¡Ay, caramba!", "Frank", Green, Blue);
+
+ // Named arguments help to disambiguate this problem
+ make_character(catch_phrase = "¡Ay, caramba!", name = "Frank",
+ least_favourite_colour = Green, favourite_colour = Blue);
+
+
+ // The named arguments can be specifed in any order.
+ make_character(favourite_colour = Octarine, catch_phrase = "U wot m8!",
+ least_favourite_colour = Green, name = "Dennis");
+
+
+ // NOTE: You cannot mix named arguments with normal values
+ /*
+ make_character("Dennis",
+ favourite_colour = Octarine, catch_phrase = "U wot m8!",
+ least_favourite_colour = Green);
+ */
+
+
+ // Named arguments can also aid with default arguments
+ numerous_things :: proc(s: string, a := 1, b := 2, c := 3.14,
+ d := "The Best String!", e := false, f := 10.3/3.1, g := false) {
+ g_str := g ? "true" : "false";
+ fmt.printf("How many?! %s: %v\n", s, g_str);
+ }
+
+ numerous_things("First");
+ numerous_things(s = "Second", g = true);
+
+
+ // Default values can be placed anywhere, not just at the end like in other languages
+ weird :: proc(pre: string, mid: int = 0, post: string) {
+ fmt.println(pre, mid, post);
+ }
+
+ weird("How many things", 42, "huh?");
+ weird(pre = "Prefix", post = "Pat");
+
+}
+
+
+default_return_values :: proc() {
+ foo :: proc(x: int) -> (first: string = "Hellope", second := "world!") {
+ match x {
+ case 0: return;
+ case 1: return "Goodbye";
+ case 2: return "Goodbye", "cruel world...";
+ case 3: return second = "cruel world...", first = "Goodbye";
+ }
+
+ return second = "my old friend.";
+ }
+
+ fmt.printf("%s %s\n", foo(0));
+ fmt.printf("%s %s\n", foo(1));
+ fmt.printf("%s %s\n", foo(2));
+ fmt.printf("%s %s\n", foo(3));
+ fmt.printf("%s %s\n", foo(4));
+ fmt.println();
+
+
+ // A more "real" example
+ Error :: enum {
+ None,
+ WhyTheNumberThree,
+ TenIsTooBig,
+ };
+
+ Entity :: struct {
+ name: string;
+ id: u32;
+ }
+
+ some_thing :: proc(input: int) -> (result: ^Entity = nil, err := Error.None) {
+ match {
+ case input == 3: return err = Error.WhyTheNumberThree;
+ case input >= 10: return err = Error.TenIsTooBig;
+ }
+
+ e := new(Entity);
+ e.id = u32(input);
+
+ return result = e;
+ }
+}
+
+call_location :: proc() {
+ amazing :: proc(n: int, using loc := #caller_location) {
+ fmt.printf("%s(%d:%d) just asked to do something amazing.\n",
+ fully_pathed_filename, line, column);
+ fmt.printf("Normal -> %d\n", n);
+ fmt.printf("Amazing -> %d\n", n+1);
+ fmt.println();
+ }
+
+ loc := #location(main);
+ fmt.println("`main` is located at", loc);
+
+ fmt.println("This line is located at", #location());
+ fmt.println();
+
+ amazing(3);
+ amazing(4, #location(call_location));
+
+ // See _preload.odin for the implementations of `assert` and `panic`
+
+}
+
+
+explicit_parametric_polymorphic_procedures :: proc() {
+ // This is how `new` is actually implemented, see _preload.odin
+ alloc_type :: proc(T: type) -> ^T do return cast(^T)alloc(size_of(T), align_of(T));
+
+ int_ptr := alloc_type(int);
+ defer free(int_ptr);
+ int_ptr^ = 137;
+ fmt.println(int_ptr, int_ptr^);
+
+ // Named arguments work too!
+ another_ptr := alloc_type(T = f32);
+ defer free(another_ptr);
+
+
+ add :: proc(T: type, args: ...T) -> T {
+ res: T;
+ for arg in args do res += arg;
+ return res;
+ }
+
+ fmt.println("add =", add(int, 1, 2, 3, 4, 5, 6));
+
+ swap :: proc(T: type, a, b: ^T) {
+ tmp := a^;
+ a^ = b^;
+ b^ = tmp;
+ }
+
+ a, b: int = 3, 4;
+ fmt.println("Pre-swap:", a, b);
+ swap(int, &a, &b);
+ fmt.println("Post-swap:", a, b);
+ a, b = b, a; // Or use this syntax for this silly example case
+
+
+ Vector2 :: struct {x, y: f32;};
+ {
+ // A more complicated example using subtyping
+ // Something like this could be used in a game
+
+ Entity :: struct {
+ using position: Vector2;
+ flags: u64;
+ id: u64;
+ derived: any;
+ }
+
+ Rock :: struct {
+ using entity: Entity;
+ heavy: bool;
+ }
+ Door :: struct {
+ using entity: Entity;
+ open: bool;
+ }
+ Monster :: struct {
+ using entity: Entity;
+ is_robot: bool;
+ is_zombie: bool;
+ }
+
+ new_entity :: proc(T: type, x, y: f32) -> ^T {
+ result := new(T);
+ result.derived = result^;
+ result.x = x;
+ result.y = y;
+
+ return result;
+ }
+
+ entities: [dynamic]^Entity;
+
+ rock := new_entity(Rock, 3, 5);
+
+ // Named arguments work too!
+ door := new_entity(T = Door, x = 3, y = 6);
+
+ // And named arguments can be any order
+ monster := new_entity(
+ y = 1,
+ x = 2,
+ T = Monster,
+ );
+
+ append(&entities, rock, door, monster);
+
+ fmt.println("Subtyping");
+ for entity in entities {
+ match e in entity.derived {
+ case Rock: fmt.println("Rock", e.x, e.y);
+ case Door: fmt.println("Door", e.x, e.y);
+ case Monster: fmt.println("Monster", e.x, e.y);
+ }
+ }
+ }
+ {
+ Entity :: struct {
+ using position: Vector2;
+ flags: u64;
+ id: u64;
+ variant: union { Rock, Door, Monster };
+ }
+
+ Rock :: struct {
+ using entity: ^Entity;
+ heavy: bool;
+ }
+ Door :: struct {
+ using entity: ^Entity;
+ open: bool;
+ }
+ Monster :: struct {
+ using entity: ^Entity;
+ is_robot: bool;
+ is_zombie: bool;
+ }
+
+ new_entity :: proc(T: type, x, y: f32) -> ^T {
+ result := new(Entity);
+ result.variant = T{entity = result};
+ result.x = x;
+ result.y = y;
+
+ return cast(^T)&result.variant;
+ }
+
+ entities: [dynamic]^Entity;
+
+ rock := new_entity(Rock, 3, 5);
+
+ // Named arguments work too!
+ door := new_entity(T = Door, x = 3, y = 6);
+
+ // And named arguments can be any order
+ monster := new_entity(
+ y = 1,
+ x = 2,
+ T = Monster,
+ );
+
+ append(&entities, rock, door, monster);
+
+ fmt.println("Union");
+ for entity in entities {
+ match e in entity.variant {
+ case Rock: fmt.println("Rock", e.x, e.y);
+ case Door: fmt.println("Door", e.x, e.y);
+ case Monster: fmt.println("Monster", e.x, e.y);
+ }
+ }
+ }
+}
+
+
+implicit_polymorphic_assignment :: proc() {
+ yep :: proc(p: proc(x: int)) {
+ p(123);
+ }
+
+ frank :: proc(x: $T) do fmt.println("frank ->", x);
+ tim :: proc(x, y: $T) do fmt.println("tim ->", x, y);
+ yep(frank);
+ // yep(tim);
+}
+
+
+
+
+main :: proc() {
+/*
+ foo :: proc(x: i64, y: f32) do fmt.println("#1", x, y);
+ foo :: proc(x: type, y: f32) do fmt.println("#2", type_info(x), y);
+ foo :: proc(x: type) do fmt.println("#3", type_info(x));
+
+ f :: foo;
+
+ f(y = 3785.1546, x = 123);
+ f(x = int, y = 897.513);
+ f(x = f32);
+
+ general_stuff();
+ foreign_blocks();
+ default_arguments();
+ named_arguments();
+ default_return_values();
+ call_location();
+ explicit_parametric_polymorphic_procedures();
+ implicit_polymorphic_assignment();
+
+
+ // Command line argument(s)!
+ // -opt=0,1,2,3
+*/
+/*
+ program := "+ + * - /";
+ accumulator := 0;
+
+ for token in program {
+ match token {
+ case '+': accumulator += 1;
+ case '-': accumulator -= 1;
+ case '*': accumulator *= 2;
+ case '/': accumulator /= 2;
+ case: // Ignore everything else
+ }
+ }
+
+ fmt.printf("The program \"%s\" calculates the value %d\n",
+ program, accumulator);
+*/
+}
diff --git a/src/check_decl.cpp b/src/check_decl.cpp
index 96827590d..7c33cf9e5 100644
--- a/src/check_decl.cpp
+++ b/src/check_decl.cpp
@@ -558,7 +558,7 @@ void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count
if (type_expr != nullptr) {
e->type = check_type(c, type_expr);
}
- if (e->type != nullptr && is_type_polymorphic(e->type)) {
+ if (e->type != nullptr && is_type_polymorphic(base_type(e->type))) {
error(e->token, "Invalid use of a polymorphic type in %.*s", LIT(context_name));
e->type = t_invalid;
}
diff --git a/src/check_expr.cpp b/src/check_expr.cpp
index a5557ee86..47151cc74 100644
--- a/src/check_expr.cpp
+++ b/src/check_expr.cpp
@@ -12,6 +12,7 @@ enum CallArgumentError {
CallArgumentError_ParameterNotFound,
CallArgumentError_ParameterMissing,
CallArgumentError_DuplicateParameter,
+ CallArgumentError_NoneConstantParameter,
};
enum CallArgumentErrorMode {
@@ -972,7 +973,7 @@ Entity *make_names_field_for_record(Checker *c, Scope *scope) {
return e;
}
-void check_struct_type(Checker *c, Type *struct_type, AstNode *node) {
+void check_struct_type(Checker *c, Type *struct_type, AstNode *node, Array<Operand> *poly_operands) {
GB_ASSERT(is_type_struct(struct_type));
ast_node(st, StructType, node);
@@ -984,11 +985,132 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node) {
min_field_count += f->names.count;
case_end;
}
-
}
struct_type->Record.names = make_names_field_for_record(c, c->context.scope);
- auto fields = check_fields(c, node, st->fields, min_field_count, str_lit("struct"));
+ Type *polymorphic_params = nullptr;
+ bool is_polymorphic = false;
+ if (st->polymorphic_params != nullptr) {
+ ast_node(field_list, FieldList, st->polymorphic_params);
+ Array<AstNode *> params = field_list->list;
+ if (params.count != 0) {
+ isize variable_count = 0;
+ for_array(i, params) {
+ AstNode *field = params[i];
+ if (ast_node_expect(field, AstNode_Field)) {
+ ast_node(f, Field, field);
+ variable_count += gb_max(f->names.count, 1);
+ }
+ }
+
+ Array<Entity *> entities = {};
+ array_init(&entities, c->allocator, variable_count);
+
+ for_array(i, params) {
+ AstNode *param = params[i];
+ if (param->kind != AstNode_Field) {
+ continue;
+ }
+ ast_node(p, Field, param);
+ AstNode *type_expr = p->type;
+ Type *type = nullptr;
+ bool is_type_param = false;
+ bool is_type_polymorphic_type = false;
+ if (type_expr == nullptr) {
+ error(param, "Expected a type for this parameter");
+ continue;
+ }
+ if (type_expr->kind == AstNode_Ellipsis) {
+ type_expr = type_expr->Ellipsis.expr;
+ error(param, "A polymorphic parameter cannot be variadic");
+ }
+ if (type_expr->kind == AstNode_TypeType) {
+ is_type_param = true;
+ type = make_type_generic(c->allocator, 0, str_lit(""));
+ } else {
+ type = check_type(c, type_expr);
+ if (is_type_polymorphic(type)) {
+ is_type_polymorphic_type = true;
+ }
+ }
+
+ if (type == nullptr) {
+ error(params[i], "Invalid parameter type");
+ type = t_invalid;
+ }
+ if (is_type_untyped(type)) {
+ if (is_type_untyped_undef(type)) {
+ error(params[i], "Cannot determine parameter type from ---");
+ } else {
+ error(params[i], "Cannot determine parameter type from a nil");
+ }
+ type = t_invalid;
+ }
+
+ if (is_type_polymorphic_type) {
+ gbString str = type_to_string(type);
+ error(params[i], "Parameter types cannot be polymorphic, got %s", str);
+ gb_string_free(str);
+ type = t_invalid;
+ }
+
+ if (!is_type_param && !is_type_constant_type(type)) {
+ gbString str = type_to_string(type);
+ error(params[i], "A parameter must be a valid constant type, got %s", str);
+ gb_string_free(str);
+ }
+
+ Scope *scope = c->context.scope;
+ for_array(j, p->names) {
+ AstNode *name = p->names[j];
+ if (!ast_node_expect(name, AstNode_Ident)) {
+ continue;
+ }
+ Entity *e = nullptr;
+
+ Token token = name->Ident.token;
+
+ if (poly_operands != nullptr) {
+ Operand operand = (*poly_operands)[entities.count];
+ if (is_type_param) {
+ GB_ASSERT(operand.mode == Addressing_Type);
+ e = make_entity_type_name(c->allocator, scope, token, operand.type);
+ e->TypeName.is_type_alias = true;
+ } else {
+ GB_ASSERT(operand.mode == Addressing_Constant);
+ e = make_entity_constant(c->allocator, scope, token, operand.type, operand.value);
+ }
+ } else {
+ if (is_type_param) {
+ e = make_entity_type_name(c->allocator, scope, token, type);
+ e->TypeName.is_type_alias = true;
+ } else {
+ e = make_entity_constant(c->allocator, scope, token, type, empty_exact_value);
+ }
+ }
+
+ add_entity(c, scope, name, e);
+ array_add(&entities, e);
+ }
+ }
+
+ if (entities.count > 0) {
+ Type *tuple = make_type_tuple(c->allocator);
+ tuple->Tuple.variables = entities.data;
+ tuple->Tuple.variable_count = entities.count;
+
+ polymorphic_params = tuple;
+ }
+ }
+ }
+
+ is_polymorphic = polymorphic_params != nullptr && poly_operands == nullptr;
+
+ Array<Entity *> fields = {};
+
+ if (!is_polymorphic) {
+ fields = check_fields(c, node, st->fields, min_field_count, str_lit("struct"));
+ }
struct_type->Record.scope = c->context.scope;
struct_type->Record.is_packed = st->is_packed;
@@ -996,6 +1118,9 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node) {
struct_type->Record.fields = fields.data;
struct_type->Record.fields_in_src_order = fields.data;
struct_type->Record.field_count = fields.count;
+ struct_type->Record.polymorphic_params = polymorphic_params;
+ struct_type->Record.is_polymorphic = is_polymorphic;
+
type_set_offsets(c->allocator, struct_type);
@@ -1703,53 +1828,55 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari
for_array(j, p->names) {
AstNode *name = p->names[j];
- if (ast_node_expect(name, AstNode_Ident)) {
- Entity *param = nullptr;
- if (is_type_param) {
- if (operands != nullptr) {
- Operand o = (*operands)[variable_index];
- if (o.mode == Addressing_Type) {
- type = o.type;
- } else {
- if (!c->context.no_polymorphic_errors) {
- error(o.expr, "Expected a type to assign to the type parameter");
- }
- success = false;
- type = t_invalid;
- }
- }
- param = make_entity_type_name(c->allocator, scope, name->Ident.token, type);
- param->TypeName.is_type_alias = true;
- } else {
- if (operands != nullptr && is_type_polymorphic_type) {
- Operand op = (*operands)[variable_index];
- type = determine_type_from_polymorphic(c, type, op);
- if (type == t_invalid) {
- success = false;
- }
- }
+ if (!ast_node_expect(name, AstNode_Ident)) {
+ continue;
+ }
- if (p->flags&FieldFlag_no_alias) {
- if (!is_type_pointer(type)) {
- error(params[i], "`#no_alias` can only be applied to fields of pointer type");
- p->flags &= ~FieldFlag_no_alias; // Remove the flag
+ Entity *param = nullptr;
+ if (is_type_param) {
+ if (operands != nullptr) {
+ Operand o = (*operands)[variable_index];
+ if (o.mode == Addressing_Type) {
+ type = o.type;
+ } else {
+ if (!c->context.no_polymorphic_errors) {
+ error(o.expr, "Expected a type to assign to the type parameter");
}
+ success = false;
+ type = t_invalid;
+ }
+ }
+ param = make_entity_type_name(c->allocator, scope, name->Ident.token, type);
+ param->TypeName.is_type_alias = true;
+ } else {
+ if (operands != nullptr && is_type_polymorphic_type) {
+ Operand op = (*operands)[variable_index];
+ type = determine_type_from_polymorphic(c, type, op);
+ if (type == t_invalid) {
+ success = false;
}
-
- param = make_entity_param(c->allocator, scope, name->Ident.token, type,
- (p->flags&FieldFlag_using) != 0, false);
- param->Variable.default_value = value;
- param->Variable.default_is_nil = default_is_nil;
- param->Variable.default_is_location = default_is_location;
-
}
+
if (p->flags&FieldFlag_no_alias) {
- param->flags |= EntityFlag_NoAlias;
+ if (!is_type_pointer(type)) {
+ error(params[i], "`#no_alias` can only be applied to fields of pointer type");
+ p->flags &= ~FieldFlag_no_alias; // Remove the flag
+ }
}
- add_entity(c, scope, name, param);
- variables[variable_index++] = param;
+ param = make_entity_param(c->allocator, scope, name->Ident.token, type,
+ (p->flags&FieldFlag_using) != 0, false);
+ param->Variable.default_value = value;
+ param->Variable.default_is_nil = default_is_nil;
+ param->Variable.default_is_location = default_is_location;
+
+ }
+ if (p->flags&FieldFlag_no_alias) {
+ param->flags |= EntityFlag_NoAlias;
}
+
+ add_entity(c, scope, name, param);
+ variables[variable_index++] = param;
}
}
@@ -2358,7 +2485,7 @@ i64 check_array_or_map_count(Checker *c, AstNode *e, bool is_map) {
if (count >= 0) {
return count;
}
- error(e, "Invalid array count");
+ error(e, "Invalid negative array count %lld", cast(long long)count);
}
return 0;
}
@@ -2663,7 +2790,7 @@ bool check_type_internal(Checker *c, AstNode *e, Type **type, Type *named_type)
*type = make_type_struct(c->allocator);
set_base_type(named_type, *type);
check_open_scope(c, e);
- check_struct_type(c, *type, e);
+ check_struct_type(c, *type, e, nullptr);
check_close_scope(c);
(*type)->Record.node = e;
return true;
@@ -6114,8 +6241,263 @@ Entity *find_using_index_expr(Type *t) {
return nullptr;
}
+isize lookup_polymorphic_struct_parameter(TypeRecord *st, String parameter_name) {
+ if (!st->is_polymorphic) return -1;
+
+ TypeTuple *params = &st->polymorphic_params->Tuple;
+ isize param_count = params->variable_count;
+ for (isize i = 0; i < param_count; i++) {
+ Entity *e = params->variables[i];
+ String name = e->token.string;
+ if (is_blank_ident(name)) {
+ continue;
+ }
+ if (name == parameter_name) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+CallArgumentError check_polymorphic_struct_type(Checker *c, Operand *operand, AstNode *call) {
+ ast_node(ce, CallExpr, call);
+
+ Type *original_type = operand->type;
+ Type *struct_type = base_type(operand->type);
+ GB_ASSERT(is_type_struct(struct_type));
+ TypeRecord *st = &struct_type->Record;
+ GB_ASSERT(st->is_polymorphic);
+
+ bool show_error = true;
+
+ Array<Operand> operands = {};
+ defer (array_free(&operands));
+
+ bool named_fields = false;
+
+ if (is_call_expr_field_value(ce)) {
+ named_fields = true;
+ array_init_count(&operands, heap_allocator(), ce->args.count);
+ for_array(i, ce->args) {
+ AstNode *arg = ce->args[i];
+ ast_node(fv, FieldValue, arg);
+ check_expr_or_type(c, &operands[i], fv->value);
+ }
+
+ bool vari_expand = (ce->ellipsis.pos.line != 0);
+ if (vari_expand) {
+ error(ce->ellipsis, "Invalid use of `..` in a polymorphic type call`");
+ }
+
+ } else {
+ array_init(&operands, heap_allocator(), 2*ce->args.count);
+ check_unpack_arguments(c, -1, &operands, ce->args, false);
+ }
+
+ CallArgumentError err = CallArgumentError_None;
+
+ TypeTuple *tuple = &st->polymorphic_params->Tuple;
+ isize param_count = tuple->variable_count;
+
+ Array<Operand> ordered_operands = operands;
+ if (named_fields) {
+ bool *visited = gb_alloc_array(c->allocator, bool, param_count);
+
+ array_init_count(&ordered_operands, c->tmp_allocator, param_count);
+
+ for_array(i, ce->args) {
+ AstNode *arg = ce->args[i];
+ ast_node(fv, FieldValue, arg);
+ if (fv->field->kind != AstNode_Ident) {
+ if (show_error) {
+ gbString expr_str = expr_to_string(fv->field);
+ error(arg, "Invalid parameter name `%s` in polymorphic type call", expr_str);
+ gb_string_free(expr_str);
+ }
+ err = CallArgumentError_InvalidFieldValue;
+ continue;
+ }
+ String name = fv->field->Ident.token.string;
+ isize index = lookup_polymorphic_struct_parameter(st, name);
+ if (index < 0) {
+ if (show_error) {
+ error(arg, "No parameter named `%.*s` for this polymorphic type", LIT(name));
+ }
+ err = CallArgumentError_ParameterNotFound;
+ continue;
+ }
+ if (visited[index]) {
+ if (show_error) {
+ error(arg, "Duplicate parameter `%.*s` in polymorphic type", LIT(name));
+ }
+ err = CallArgumentError_DuplicateParameter;
+ continue;
+ }
+
+ visited[index] = true;
+ ordered_operands[index] = operands[i];
+ }
+
+ for (isize i = 0; i < param_count; i++) {
+ if (!visited[i]) {
+ Entity *e = tuple->variables[i];
+ if (is_blank_ident(e->token)) {
+ continue;
+ }
+
+ if (show_error) {
+ if (e->kind == Entity_TypeName) {
+ error(call, "Type parameter `%.*s` is missing in polymorphic type call",
+ LIT(e->token.string));
+ } else {
+ gbString str = type_to_string(e->type);
+ error(call, "Parameter `%.*s` of type `%s` is missing in polymorphic type call",
+ LIT(e->token.string), str);
+ gb_string_free(str);
+ }
+ }
+ err = CallArgumentError_ParameterMissing;
+ }
+ }
+ }
+
+ if (err != 0) {
+ operand->mode = Addressing_Invalid;
+ return err;
+ }
+
+ i64 score = 0;
+ for (isize i = 0; i < param_count; i++) {
+ Operand *o = &ordered_operands[i];
+ if (o->mode == Addressing_Invalid) {
+ continue;
+ }
+ Entity *e = tuple->variables[i];
+
+ if (e->kind == Entity_TypeName) {
+ if (o->mode != Addressing_Type) {
+ if (show_error) {
+ error(o->expr, "Expected a type for the argument `%.*s`", LIT(e->token.string));
+ }
+ err = CallArgumentError_WrongTypes;
+ }
+ if (are_types_identical(e->type, o->type)) {
+ score += assign_score_function(1);
+ } else {
+ score += assign_score_function(10);
+ }
+ } else {
+ i64 s = 0;
+ if (!check_is_assignable_to_with_score(c, o, e->type, &s)) {
+ if (show_error) {
+ check_assignment(c, o, e->type, str_lit("polymorphic type argument"));
+ }
+ err = CallArgumentError_WrongTypes;
+ }
+ o->type = e->type;
+ if (o->mode != Addressing_Constant) {
+ if (show_error) {
+ error(o->expr, "Expected a constant value for this polymorphic type argument");
+ }
+ err = CallArgumentError_NoneConstantParameter;
+ }
+ score += s;
+ }
+ }
+
+ if (param_count < ordered_operands.count) {
+ error(call, "Too many polymorphic type arguments, expected %td, got %td", param_count, ordered_operands.count);
+ err = CallArgumentError_TooManyArguments;
+ } else if (param_count > ordered_operands.count) {
+ error(call, "Too few polymorphic type arguments, expected %td, got %td", param_count, ordered_operands.count);
+ err = CallArgumentError_TooFewArguments;
+ }
+
+ if (err == 0) {
+ // TODO(bill): Check for previous types
+ gbAllocator a = c->allocator;
+
+ auto *found_gen_types = map_get(&c->info.gen_types, hash_pointer(original_type));
+
+ if (found_gen_types != nullptr) {
+ for_array(i, *found_gen_types) {
+ Entity *e = (*found_gen_types)[i];
+ Type *t = base_type(e->type);
+ TypeTuple *tuple = &t->Record.polymorphic_params->Tuple;
+ bool ok = true;
+ GB_ASSERT(param_count == tuple->variable_count);
+ for (isize j = 0; j < param_count; j++) {
+ Entity *p = tuple->variables[j];
+ Operand o = ordered_operands[j];
+ if (p->kind == Entity_TypeName) {
+ if (!are_types_identical(o.type, p->type)) {
+ ok = false;
+ }
+ } else if (p->kind == Entity_Constant) {
+ if (!are_types_identical(o.type, p->type)) {
+ ok = false;
+ }
+ if (!compare_exact_values(Token_CmpEq, o.value, p->Constant.value)) {
+ ok = false;
+ }
+ } else {
+ GB_PANIC("Unknown entity kind");
+ }
+ }
+ if (ok) {
+ operand->mode = Addressing_Type;
+ operand->type = e->type;
+ return err;
+ }
+ }
+ }
+
+ String generated_name = make_string_c(expr_to_string(call));
+
+ Type *named_type = make_type_named(a, generated_name, nullptr, nullptr);
+ Type *struct_type = make_type_struct(a);
+ AstNode *node = clone_ast_node(a, st->node);
+ set_base_type(named_type, struct_type);
+ check_open_scope(c, node);
+ check_struct_type(c, struct_type, node, &ordered_operands);
+ check_close_scope(c);
+ struct_type->Record.node = node;
+
+ Entity *e = nullptr;
+
+ {
+ Token token = ast_node_token(node);
+ token.kind = Token_String;
+ token.string = generated_name;
+
+ AstNode *node = gb_alloc_item(a, AstNode);
+ node->kind = AstNode_Ident;
+ node->Ident.token = token;
+
+ e = make_entity_type_name(a, st->scope->parent, token, named_type);
+ add_entity(c, st->scope->parent, node, e);
+ add_entity_use(c, node, e);
+ }
+
+ named_type->Named.type_name = e;
+
+ if (found_gen_types) {
+ array_add(found_gen_types, e);
+ } else {
+ Array<Entity *> array = {};
+ array_init(&array, heap_allocator());
+ array_add(&array, e);
+ map_set(&c->info.gen_types, hash_pointer(original_type), array);
+ }
+
+ operand->mode = Addressing_Type;
+ operand->type = named_type;
+ }
+ return err;
+}
+
+
ExprKind check_call_expr(Checker *c, Operand *operand, AstNode *call) {
- GB_ASSERT(call->kind == AstNode_CallExpr);
ast_node(ce, CallExpr, call);
if (ce->proc != nullptr &&
ce->proc->kind == AstNode_BasicDirective) {
@@ -6170,28 +6552,43 @@ ExprKind check_call_expr(Checker *c, Operand *operand, AstNode *call) {
if (operand->mode == Addressing_Type) {
Type *t = operand->type;
- gbString str = type_to_string(t);
- defer (gb_string_free(str));
-
- operand->mode = Addressing_Invalid;
- isize arg_count = ce->args.count;
- switch (arg_count) {
- case 0: error(call, "Missing argument in conversion to `%s`", str); break;
- default: error(call, "Too many arguments in conversion to `%s`", str); break;
- case 1: {
- AstNode *arg = ce->args[0];
- if (arg->kind == AstNode_FieldValue) {
- error(call, "`field = value` cannot be used in a type conversion");
- arg = arg->FieldValue.value;
- // NOTE(bill): Carry on the cast regardless
+ if (is_type_polymorphic_struct(t)) {
+ auto err = check_polymorphic_struct_type(c, operand, call);
+ if (err == 0) {
+ AstNode *ident = operand->expr;
+ while (ident->kind == AstNode_SelectorExpr) {
+ AstNode *s = ident->SelectorExpr.selector;
+ ident = s;
+ }
+ add_entity_use(c, ident, entity_of_ident(&c->info, ident));
+ add_type_and_value(&c->info, call, Addressing_Type, operand->type, empty_exact_value);
+ } else {
+ operand->mode = Addressing_Invalid;
+ operand->type = t_invalid;
}
- check_expr(c, operand, arg);
- if (operand->mode != Addressing_Invalid) {
- check_cast(c, operand, t);
+ } else {
+ gbString str = type_to_string(t);
+ defer (gb_string_free(str));
+
+ operand->mode = Addressing_Invalid;
+ isize arg_count = ce->args.count;
+ switch (arg_count) {
+ case 0: error(call, "Missing argument in conversion to `%s`", str); break;
+ default: error(call, "Too many arguments in conversion to `%s`", str); break;
+ case 1: {
+ AstNode *arg = ce->args[0];
+ if (arg->kind == AstNode_FieldValue) {
+ error(call, "`field = value` cannot be used in a type conversion");
+ arg = arg->FieldValue.value;
+ // NOTE(bill): Carry on the cast regardless
+ }
+ check_expr(c, operand, arg);
+ if (operand->mode != Addressing_Invalid) {
+ check_cast(c, operand, t);
+ }
+ } break;
}
- } break;
}
-
return Expr_Expr;
}
diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp
index 6bf873bd3..51e6adf79 100644
--- a/src/check_stmt.cpp
+++ b/src/check_stmt.cpp
@@ -1666,150 +1666,151 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
case_end;
case_ast_node(vd, ValueDecl, node);
- if (vd->is_mutable) {
- Entity **entities = gb_alloc_array(c->allocator, Entity *, vd->names.count);
- isize entity_count = 0;
+ if (!vd->is_mutable) {
+ break;
+ }
+ Entity **entities = gb_alloc_array(c->allocator, Entity *, vd->names.count);
+ isize entity_count = 0;
- if (vd->flags & VarDeclFlag_thread_local) {
- vd->flags &= ~VarDeclFlag_thread_local;
- error(node, "`thread_local` may only be applied to a variable declaration");
- }
+ if (vd->flags & VarDeclFlag_thread_local) {
+ vd->flags &= ~VarDeclFlag_thread_local;
+ error(node, "`thread_local` may only be applied to a variable declaration");
+ }
- for_array(i, vd->names) {
- AstNode *name = vd->names[i];
- Entity *entity = nullptr;
- if (name->kind != AstNode_Ident) {
- error(name, "A variable declaration must be an identifier");
- } else {
- Token token = name->Ident.token;
- String str = token.string;
- Entity *found = nullptr;
- // NOTE(bill): Ignore assignments to `_`
- if (!is_blank_ident(str)) {
- found = current_scope_lookup_entity(c->context.scope, str);
- }
- if (found == nullptr) {
- entity = make_entity_variable(c->allocator, c->context.scope, token, nullptr, false);
- entity->identifier = name;
-
- AstNode *fl = c->context.curr_foreign_library;
- if (fl != nullptr) {
- GB_ASSERT(fl->kind == AstNode_Ident);
- entity->Variable.is_foreign = true;
- entity->Variable.foreign_library_ident = fl;
- }
- } else {
- TokenPos pos = found->token.pos;
- error(token,
- "Redeclaration of `%.*s` in this scope\n"
- "\tat %.*s(%td:%td)",
- LIT(str), LIT(pos.file), pos.line, pos.column);
- entity = found;
- }
- }
- if (entity == nullptr) {
- entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name));
+ for_array(i, vd->names) {
+ AstNode *name = vd->names[i];
+ Entity *entity = nullptr;
+ if (name->kind != AstNode_Ident) {
+ error(name, "A variable declaration must be an identifier");
+ } else {
+ Token token = name->Ident.token;
+ String str = token.string;
+ Entity *found = nullptr;
+ // NOTE(bill): Ignore assignments to `_`
+ if (!is_blank_ident(str)) {
+ found = current_scope_lookup_entity(c->context.scope, str);
}
- entity->parent_proc_decl = c->context.curr_proc_decl;
- entities[entity_count++] = entity;
- }
-
- Type *init_type = nullptr;
- if (vd->type) {
- init_type = check_type(c, vd->type, nullptr);
- if (init_type == nullptr) {
- init_type = t_invalid;
- } else if (is_type_polymorphic(init_type)) {
- error(vd->type, "Invalid use of a polymorphic type in variable declaration");
- init_type = t_invalid;
+ if (found == nullptr) {
+ entity = make_entity_variable(c->allocator, c->context.scope, token, nullptr, false);
+ entity->identifier = name;
+
+ AstNode *fl = c->context.curr_foreign_library;
+ if (fl != nullptr) {
+ GB_ASSERT(fl->kind == AstNode_Ident);
+ entity->Variable.is_foreign = true;
+ entity->Variable.foreign_library_ident = fl;
+ }
+ } else {
+ TokenPos pos = found->token.pos;
+ error(token,
+ "Redeclaration of `%.*s` in this scope\n"
+ "\tat %.*s(%td:%td)",
+ LIT(str), LIT(pos.file), pos.line, pos.column);
+ entity = found;
}
}
+ if (entity == nullptr) {
+ entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name));
+ }
+ entity->parent_proc_decl = c->context.curr_proc_decl;
+ entities[entity_count++] = entity;
+ }
- for (isize i = 0; i < entity_count; i++) {
- Entity *e = entities[i];
- GB_ASSERT(e != nullptr);
- if (e->flags & EntityFlag_Visited) {
- e->type = t_invalid;
- continue;
- }
- e->flags |= EntityFlag_Visited;
+ Type *init_type = nullptr;
+ if (vd->type != nullptr) {
+ init_type = check_type(c, vd->type, nullptr);
+ if (init_type == nullptr) {
+ init_type = t_invalid;
+ } else if (is_type_polymorphic(base_type(init_type))) {
+ error(vd->type, "Invalid use of a polymorphic type in variable declaration");
+ init_type = t_invalid;
+ }
+ }
- if (e->type == nullptr) {
- e->type = init_type;
- }
+ for (isize i = 0; i < entity_count; i++) {
+ Entity *e = entities[i];
+ GB_ASSERT(e != nullptr);
+ if (e->flags & EntityFlag_Visited) {
+ e->type = t_invalid;
+ continue;
}
+ e->flags |= EntityFlag_Visited;
- check_arity_match(c, vd);
- check_init_variables(c, entities, entity_count, vd->values, str_lit("variable declaration"));
+ if (e->type == nullptr) {
+ e->type = init_type;
+ }
+ }
- for (isize i = 0; i < entity_count; i++) {
- Entity *e = entities[i];
- if (e->Variable.is_foreign) {
- if (vd->values.count > 0) {
- error(e->token, "A foreign variable declaration cannot have a default value");
- }
- init_entity_foreign_library(c, e);
+ check_arity_match(c, vd);
+ check_init_variables(c, entities, entity_count, vd->values, str_lit("variable declaration"));
- String name = e->token.string;
- auto *fp = &c->info.foreigns;
- HashKey key = hash_string(name);
- Entity **found = map_get(fp, key);
- if (found) {
- Entity *f = *found;
- TokenPos pos = f->token.pos;
- Type *this_type = base_type(e->type);
- Type *other_type = base_type(f->type);
- if (!are_types_identical(this_type, other_type)) {
- error(e->token,
- "Foreign entity `%.*s` previously declared elsewhere with a different type\n"
- "\tat %.*s(%td:%td)",
- LIT(name), LIT(pos.file), pos.line, pos.column);
- }
- } else {
- map_set(fp, key, e);
+ for (isize i = 0; i < entity_count; i++) {
+ Entity *e = entities[i];
+ if (e->Variable.is_foreign) {
+ if (vd->values.count > 0) {
+ error(e->token, "A foreign variable declaration cannot have a default value");
+ }
+ init_entity_foreign_library(c, e);
+
+ String name = e->token.string;
+ auto *fp = &c->info.foreigns;
+ HashKey key = hash_string(name);
+ Entity **found = map_get(fp, key);
+ if (found) {
+ Entity *f = *found;
+ TokenPos pos = f->token.pos;
+ Type *this_type = base_type(e->type);
+ Type *other_type = base_type(f->type);
+ if (!are_types_identical(this_type, other_type)) {
+ error(e->token,
+ "Foreign entity `%.*s` previously declared elsewhere with a different type\n"
+ "\tat %.*s(%td:%td)",
+ LIT(name), LIT(pos.file), pos.line, pos.column);
}
+ } else {
+ map_set(fp, key, e);
}
- add_entity(c, c->context.scope, e->identifier, e);
}
+ add_entity(c, c->context.scope, e->identifier, e);
+ }
- if ((vd->flags & VarDeclFlag_using) != 0) {
- Token token = ast_node_token(node);
- if (vd->type != nullptr && entity_count > 1) {
- error(token, "`using` can only be applied to one variable of the same type");
- // TODO(bill): Should a `continue` happen here?
- }
+ if ((vd->flags & VarDeclFlag_using) != 0) {
+ Token token = ast_node_token(node);
+ if (vd->type != nullptr && entity_count > 1) {
+ error(token, "`using` can only be applied to one variable of the same type");
+ // TODO(bill): Should a `continue` happen here?
+ }
- for (isize entity_index = 0; entity_index < entity_count; entity_index++) {
- Entity *e = entities[entity_index];
- if (e == nullptr) {
- continue;
- }
- if (e->kind != Entity_Variable) {
- continue;
- }
- bool is_immutable = e->Variable.is_immutable;
- String name = e->token.string;
- Type *t = base_type(type_deref(e->type));
-
- if (is_type_struct(t) || is_type_raw_union(t)) {
- Scope *scope = scope_of_node(&c->info, t->Record.node);
- for_array(i, scope->elements.entries) {
- Entity *f = scope->elements.entries[i].value;
- if (f->kind == Entity_Variable) {
- Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
- uvar->Variable.is_immutable = is_immutable;
- Entity *prev = scope_insert_entity(c->context.scope, uvar);
- if (prev != nullptr) {
- error(token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string));
- return;
- }
+ for (isize entity_index = 0; entity_index < entity_count; entity_index++) {
+ Entity *e = entities[entity_index];
+ if (e == nullptr) {
+ continue;
+ }
+ if (e->kind != Entity_Variable) {
+ continue;
+ }
+ bool is_immutable = e->Variable.is_immutable;
+ String name = e->token.string;
+ Type *t = base_type(type_deref(e->type));
+
+ if (is_type_struct(t) || is_type_raw_union(t)) {
+ Scope *scope = scope_of_node(&c->info, t->Record.node);
+ for_array(i, scope->elements.entries) {
+ Entity *f = scope->elements.entries[i].value;
+ if (f->kind == Entity_Variable) {
+ Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
+ uvar->Variable.is_immutable = is_immutable;
+ Entity *prev = scope_insert_entity(c->context.scope, uvar);
+ if (prev != nullptr) {
+ error(token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string));
+ return;
}
}
- } else {
- // NOTE(bill): skip the rest to remove extra errors
- error(token, "`using` can only be applied to variables of type struct or raw_union");
- return;
}
+ } else {
+ // NOTE(bill): skip the rest to remove extra errors
+ error(token, "`using` can only be applied to variables of type struct or raw_union");
+ return;
}
}
}
diff --git a/src/checker.cpp b/src/checker.cpp
index d7882414a..1cbf5db40 100644
--- a/src/checker.cpp
+++ b/src/checker.cpp
@@ -301,6 +301,7 @@ struct CheckerInfo {
Map<ExprInfo> untyped; // Key: AstNode * | Expression -> ExprInfo
Map<Entity *> implicits; // Key: AstNode *
Map<Array<Entity *> > gen_procs; // Key: AstNode * | Identifier -> Entity
+ Map<Array<Entity *> > gen_types; // Key: Type *
Map<DeclInfo *> entities; // Key: Entity *
Map<Entity *> foreigns; // Key: String
Map<AstFile *> files; // Key: String (full path)
@@ -742,6 +743,7 @@ void init_checker_info(CheckerInfo *i) {
map_init(&i->foreigns, a);
map_init(&i->implicits, a);
map_init(&i->gen_procs, a);
+ map_init(&i->gen_types, a);
map_init(&i->type_info_map, a);
map_init(&i->files, a);
i->type_info_count = 0;
@@ -758,6 +760,7 @@ void destroy_checker_info(CheckerInfo *i) {
map_destroy(&i->foreigns);
map_destroy(&i->implicits);
map_destroy(&i->gen_procs);
+ map_destroy(&i->gen_types);
map_destroy(&i->type_info_map);
map_destroy(&i->files);
}
diff --git a/src/ir.cpp b/src/ir.cpp
index 8746b676b..3d9ef820a 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -3670,6 +3670,18 @@ void ir_pop_target_list(irProcedure *proc) {
void ir_gen_global_type_name(irModule *m, Entity *e, String name) {
if (e->type == nullptr) return;
+ if (is_type_polymorphic(base_type(e->type))) {
+ auto found = map_get(&m->info->gen_types, hash_pointer(e->type));
+ if (found == nullptr) {
+ return;
+ }
+ for_array(i, *found) {
+ Entity *e = (*found)[i];
+ ir_mangle_add_sub_type_name(m, e, name);
+ }
+ return;
+ }
+
irValue *t = ir_value_type_name(m->allocator, name, e->type);
ir_module_add_value(m, e, t);
map_set(&m->members, hash_string(name), t);
@@ -5731,8 +5743,10 @@ void ir_build_constant_value_decl(irProcedure *proc, AstNodeValueDecl *vd) {
}
bool polymorphic = is_type_polymorphic(e->type);
-
- if (!polymorphic && map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) {
+ if (polymorphic && !is_type_struct(e->type)) {
+ continue;
+ }
+ if (map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) {
// NOTE(bill): Nothing depends upon it so doesn't need to be built
continue;
}
@@ -5741,6 +5755,7 @@ void ir_build_constant_value_decl(irProcedure *proc, AstNodeValueDecl *vd) {
// NOTE(bill): Generate a new name
// parent_proc.name-guid
String ts_name = e->token.string;
+
isize name_len = proc->name.len + 1 + ts_name.len + 1 + 10 + 1;
u8 *name_text = gb_alloc_array(proc->module->allocator, u8, name_len);
i32 guid = cast(i32)proc->module->members.entries.count;
@@ -5751,6 +5766,7 @@ void ir_build_constant_value_decl(irProcedure *proc, AstNodeValueDecl *vd) {
name, e->type);
map_set(&proc->module->entity_names, hash_entity(e), name);
ir_gen_global_type_name(proc->module, e, name);
+
} else if (e->kind == Entity_Procedure) {
CheckerInfo *info = proc->module->info;
DeclInfo *decl = decl_info_of_entity(info, e);
diff --git a/src/ir_print.cpp b/src/ir_print.cpp
index 7bd79fe2e..c683f4dc6 100644
--- a/src/ir_print.cpp
+++ b/src/ir_print.cpp
@@ -340,7 +340,7 @@ void ir_print_type(irFileBuffer *f, irModule *m, Type *t) {
case Type_Named:
if (is_type_struct(t) || is_type_union(t)) {
String *name = map_get(&m->entity_names, hash_pointer(t->Named.type_name));
- GB_ASSERT_MSG(name != nullptr, "%.*s", LIT(t->Named.name));
+ GB_ASSERT_MSG(name != nullptr, "%.*s %p", LIT(t->Named.name), t->Named.type_name);
ir_print_encoded_local(f, *name);
} else {
ir_print_type(f, m, base_type(t));
diff --git a/src/parser.cpp b/src/parser.cpp
index a2d8ab1f9..ca5e59983 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -111,6 +111,7 @@ enum StmtStateFlag {
};
enum FieldFlag {
+ FieldFlag_NONE = 0,
FieldFlag_ellipsis = 1<<0,
FieldFlag_using = 1<<1,
FieldFlag_no_alias = 1<<2,
@@ -418,12 +419,13 @@ AST_NODE_KIND(_TypeBegin, "", i32) \
AstNode *elem; \
}) \
AST_NODE_KIND(StructType, "struct type", struct { \
- Token token; \
- Array<AstNode *> fields; \
- isize field_count; \
- bool is_packed; \
- bool is_ordered; \
- AstNode *align; \
+ Token token; \
+ Array<AstNode *> fields; \
+ isize field_count; \
+ AstNode * polymorphic_params; \
+ bool is_packed; \
+ bool is_ordered; \
+ AstNode * align; \
}) \
AST_NODE_KIND(UnionType, "union type", struct { \
Token token; \
@@ -866,6 +868,7 @@ AstNode *clone_ast_node(gbAllocator a, AstNode *node) {
break;
case AstNode_StructType:
n->StructType.fields = clone_ast_node_array(a, n->StructType.fields);
+ n->StructType.polymorphic_params = clone_ast_node(a, n->StructType.polymorphic_params);
n->StructType.align = clone_ast_node(a, n->StructType.align);
break;
case AstNode_UnionType:
@@ -1459,14 +1462,15 @@ AstNode *ast_vector_type(AstFile *f, Token token, AstNode *count, AstNode *elem)
}
AstNode *ast_struct_type(AstFile *f, Token token, Array<AstNode *> fields, isize field_count,
- bool is_packed, bool is_ordered, AstNode *align) {
+ AstNode *polymorphic_params, bool is_packed, bool is_ordered, AstNode *align) {
AstNode *result = make_ast_node(f, AstNode_StructType);
- result->StructType.token = token;
- result->StructType.fields = fields;
- result->StructType.field_count = field_count;
- result->StructType.is_packed = is_packed;
- result->StructType.is_ordered = is_ordered;
- result->StructType.align = align;
+ result->StructType.token = token;
+ result->StructType.fields = fields;
+ result->StructType.field_count = field_count;
+ result->StructType.polymorphic_params = polymorphic_params;
+ result->StructType.is_packed = is_packed;
+ result->StructType.is_ordered = is_ordered;
+ result->StructType.align = align;
return result;
}
@@ -2182,6 +2186,8 @@ AstNode * parse_simple_stmt (AstFile *f, StmtAllowFlag flags);
AstNode * parse_type (AstFile *f);
AstNode * parse_call_expr (AstFile *f, AstNode *operand);
AstNode * parse_record_field_list(AstFile *f, isize *name_count_);
+AstNode *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKind follow, bool allow_default_parameters, bool allow_type_token);
+
AstNode *convert_stmt_to_expr(AstFile *f, AstNode *statement, String kind) {
if (statement == nullptr) {
@@ -2205,7 +2211,7 @@ AstNode *convert_stmt_to_body(AstFile *f, AstNode *stmt) {
syntax_error(stmt, "Expected a normal statement rather than a block statement");
return stmt;
}
- GB_ASSERT(is_ast_node_stmt(stmt));
+ GB_ASSERT(is_ast_node_stmt(stmt) || is_ast_node_decl(stmt));
Token open = ast_node_token(stmt);
Token close = ast_node_token(stmt);
Array<AstNode *> stmts = make_ast_node_array(f, 1);
@@ -2430,10 +2436,21 @@ AstNode *parse_operand(AstFile *f, bool lhs) {
case Token_struct: {
Token token = expect_token(f, Token_struct);
+ AstNode *polymorphic_params = nullptr;
bool is_packed = false;
bool is_ordered = false;
AstNode *align = nullptr;
+ if (allow_token(f, Token_OpenParen)) {
+ isize param_count = 0;
+ polymorphic_params = parse_field_list(f, &param_count, 0, Token_CloseParen, false, true);
+ if (param_count == 0) {
+ syntax_error(polymorphic_params, "Expected at least 1 polymorphic parametric");
+ polymorphic_params = nullptr;
+ }
+ expect_token_after(f, Token_CloseParen, "parameter list");
+ }
+
isize prev_level = f->expr_level;
f->expr_level = -1;
@@ -2477,7 +2494,7 @@ AstNode *parse_operand(AstFile *f, bool lhs) {
decls = fields->FieldList.list;
}
- return ast_struct_type(f, token, decls, name_count, is_packed, is_ordered, align);
+ return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_ordered, align);
} break;
case Token_union: {
@@ -3394,7 +3411,6 @@ AstNode *parse_block_stmt(AstFile *f, b32 is_when) {
return parse_body(f);
}
-AstNode *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKind follow, bool allow_default_parameters);
AstNode *parse_results(AstFile *f) {
@@ -3414,7 +3430,7 @@ AstNode *parse_results(AstFile *f) {
AstNode *list = nullptr;
expect_token(f, Token_OpenParen);
- list = parse_field_list(f, nullptr, 0, Token_CloseParen, true);
+ list = parse_field_list(f, nullptr, 0, Token_CloseParen, true, false);
expect_token_after(f, Token_CloseParen, "parameter list");
return list;
}
@@ -3424,7 +3440,7 @@ AstNode *parse_proc_type(AstFile *f, Token proc_token, String *link_name_) {
AstNode *results = nullptr;
expect_token(f, Token_OpenParen);
- params = parse_field_list(f, nullptr, FieldFlag_Signature, Token_CloseParen, true);
+ params = parse_field_list(f, nullptr, FieldFlag_Signature, Token_CloseParen, true, true);
expect_token_after(f, Token_CloseParen, "parameter list");
results = parse_results(f);
@@ -3669,7 +3685,7 @@ AstNode *parse_record_field_list(AstFile *f, isize *name_count_) {
return ast_field_list(f, start_token, decls);
}
-AstNode *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKind follow, bool allow_default_parameters) {
+AstNode *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKind follow, bool allow_default_parameters, bool allow_type_token) {
TokenKind separator = Token_Comma;
Token start_token = f->curr_token;
@@ -3682,7 +3698,6 @@ AstNode *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, Tok
isize total_name_count = 0;
bool allow_ellipsis = allowed_flags&FieldFlag_ellipsis;
- bool allow_type_token = allow_default_parameters;
while (f->curr_token.kind != follow &&
f->curr_token.kind != Token_Colon &&
@@ -3691,10 +3706,9 @@ AstNode *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, Tok
AstNode *param = parse_var_type(f, allow_ellipsis, allow_type_token);
AstNodeAndFlags naf = {param, flags};
array_add(&list, naf);
- if (f->curr_token.kind != Token_Comma) {
+ if (!allow_token(f, Token_Comma)) {
break;
}
- advance_token(f);
}
if (f->curr_token.kind == Token_Colon) {
@@ -3750,7 +3764,7 @@ AstNode *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, Tok
AstNode *default_value = nullptr;
expect_token_after(f, Token_Colon, "field list");
if (f->curr_token.kind != Token_Eq) {
- type = parse_var_type(f, allow_ellipsis, allow_default_parameters);
+ type = parse_var_type(f, allow_ellipsis, allow_type_token);
}
if (allow_token(f, Token_Eq)) {
// TODO(bill): Should this be true==lhs or false==rhs?
diff --git a/src/types.cpp b/src/types.cpp
index d25f3fba9..5f3bee127 100644
--- a/src/types.cpp
+++ b/src/types.cpp
@@ -94,6 +94,8 @@ struct TypeRecord {
bool are_offsets_being_processed;
bool is_packed;
bool is_ordered;
+ bool is_polymorphic;
+ Type * polymorphic_params; // Type_Tuple
i64 custom_align; // NOTE(bill): Only used in structs at the moment
Entity * names;
@@ -937,6 +939,15 @@ bool is_type_indexable(Type *t) {
return is_type_array(t) || is_type_slice(t) || is_type_vector(t) || is_type_string(t);
}
+bool is_type_polymorphic_struct(Type *t) {
+ t = base_type(t);
+ if (t->kind == Type_Record &&
+ t->Record.kind == TypeRecord_Struct) {
+ return t->Record.is_polymorphic;
+ }
+ return false;
+}
+
bool is_type_polymorphic(Type *t) {
switch (t->kind) {
case Type_Generic:
@@ -967,7 +978,7 @@ bool is_type_polymorphic(Type *t) {
if (t->Proc.is_polymorphic) {
return true;
}
- #if 0
+ #if 1
if (t->Proc.param_count > 0 &&
is_type_polymorphic(t->Proc.params)) {
return true;
@@ -995,6 +1006,9 @@ bool is_type_polymorphic(Type *t) {
}
break;
case Type_Record:
+ if (t->Record.is_polymorphic) {
+ return true;
+ }
for (isize i = 0; i < t->Record.field_count; i++) {
if (is_type_polymorphic(t->Record.fields[i]->type)) {
return true;