From af2800b26a3f2750d0258deecf41f534af8b7034 Mon Sep 17 00:00:00 2001 From: degory Date: Tue, 12 Mar 2024 13:54:14 +0100 Subject: [PATCH] Naming convention destructuring and public fields Enhancements: - Destructure non-tuple values by field naming convention (see #1073) - Public fields (see #1110) Technical: - Optionally emit boilerplate IL assembly language from a file specified in a command line argument --- .config/dotnet-tools.json | 2 +- Directory.Build.props | 2 +- .../field-definition-1/.vscode/tasks.json | 23 +++ .../execution/field-definition-1/err.expected | 0 .../execution/field-definition-1/ghul.json | 6 + .../execution/field-definition-1/ghulflags | 0 .../execution/field-definition-1/il.expected | 0 .../execution/field-definition-1/run.expected | 1 + .../execution/field-definition-1/test.ghul | 17 +++ .../field-definition-1/warn.expected | 0 .../.vscode/tasks.json | 23 +++ .../err.expected | 0 .../naming-convention-destructure-1/ghul.json | 6 + .../naming-convention-destructure-1/ghulflags | 0 .../il.expected | 0 .../run.expected | 20 +++ .../naming-convention-destructure-1/test.ghul | 142 ++++++++++++++++++ .../warn.expected | 0 .../field-definition-1/.vscode/tasks.json | 23 +++ .../parse/field-definition-1/err.expected | 0 .../parse/field-definition-1/fail.expected | 1 + .../parse/field-definition-1/ghul.json | 6 + .../parse/field-definition-1/ghulflags | 1 + .../parse/field-definition-1/il.expected | 0 .../parse/field-definition-1/test.ghul | 7 + .../parse/field-definition-1/warn.expected | 0 .../field-definition-1/.vscode/tasks.json | 23 +++ .../semantic/field-definition-1/err.expected | 3 + .../semantic/field-definition-1/fail.expected | 1 + .../semantic/field-definition-1/ghul.json | 6 + .../semantic/field-definition-1/ghulflags | 1 + .../semantic/field-definition-1/il.expected | 0 .../semantic/field-definition-1/test.ghul | 44 ++++++ .../semantic/field-definition-1/warn.expected | 0 src/compiler/build_flags.ghul | 1 + src/driver/main.ghul | 17 ++- src/ir/values/value.ghul | 6 +- src/lexical/token.ghul | 1 + src/lexical/token_names.ghul | 1 + src/lexical/tokenizer.ghul | 1 + src/semantic/dotnet/symbol_factory.ghul | 32 ++-- src/semantic/dotnet/tuple_type_wrappers.ghul | 4 +- src/semantic/symbols/classy.ghul | 18 +-- src/semantic/symbols/closure.ghul | 8 +- src/semantic/symbols/variable.ghul | 11 +- src/semantic/types/type.ghul | 2 +- src/syntax/parsers/modifiers/modifier.ghul | 5 + .../process/add_accessors_for_properties.ghul | 14 ++ src/syntax/process/compile_expressions.ghul | 85 ++++++++--- src/syntax/process/declare_symbols.ghul | 8 +- src/syntax/process/generate_il.ghul | 3 +- src/syntax/trees/modifiers/list.ghul | 2 + src/syntax/trees/modifiers/modifier.ghul | 8 + 53 files changed, 524 insertions(+), 61 deletions(-) create mode 100644 integration-tests/execution/field-definition-1/.vscode/tasks.json create mode 100644 integration-tests/execution/field-definition-1/err.expected create mode 100644 integration-tests/execution/field-definition-1/ghul.json create mode 100644 integration-tests/execution/field-definition-1/ghulflags create mode 100644 integration-tests/execution/field-definition-1/il.expected create mode 100644 integration-tests/execution/field-definition-1/run.expected create mode 100644 integration-tests/execution/field-definition-1/test.ghul create mode 100644 integration-tests/execution/field-definition-1/warn.expected create mode 100644 integration-tests/execution/naming-convention-destructure-1/.vscode/tasks.json create mode 100644 integration-tests/execution/naming-convention-destructure-1/err.expected create mode 100644 integration-tests/execution/naming-convention-destructure-1/ghul.json create mode 100644 integration-tests/execution/naming-convention-destructure-1/ghulflags create mode 100644 integration-tests/execution/naming-convention-destructure-1/il.expected create mode 100644 integration-tests/execution/naming-convention-destructure-1/run.expected create mode 100644 integration-tests/execution/naming-convention-destructure-1/test.ghul create mode 100644 integration-tests/execution/naming-convention-destructure-1/warn.expected create mode 100644 integration-tests/parse/field-definition-1/.vscode/tasks.json create mode 100644 integration-tests/parse/field-definition-1/err.expected create mode 100644 integration-tests/parse/field-definition-1/fail.expected create mode 100644 integration-tests/parse/field-definition-1/ghul.json create mode 100644 integration-tests/parse/field-definition-1/ghulflags create mode 100644 integration-tests/parse/field-definition-1/il.expected create mode 100644 integration-tests/parse/field-definition-1/test.ghul create mode 100644 integration-tests/parse/field-definition-1/warn.expected create mode 100644 integration-tests/semantic/field-definition-1/.vscode/tasks.json create mode 100644 integration-tests/semantic/field-definition-1/err.expected create mode 100644 integration-tests/semantic/field-definition-1/fail.expected create mode 100644 integration-tests/semantic/field-definition-1/ghul.json create mode 100644 integration-tests/semantic/field-definition-1/ghulflags create mode 100644 integration-tests/semantic/field-definition-1/il.expected create mode 100644 integration-tests/semantic/field-definition-1/test.ghul create mode 100644 integration-tests/semantic/field-definition-1/warn.expected diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index ab7002773..816b1fead 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "ghul.compiler": { - "version": "0.8.15", + "version": "0.8.16", "commands": [ "ghul-compiler" ] diff --git a/Directory.Build.props b/Directory.Build.props index e611562b1..3255e344b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 0.8.16-alpha.10 + 0.8.17-alpha.33 $(NoWarn);NU1507 diff --git a/integration-tests/execution/field-definition-1/.vscode/tasks.json b/integration-tests/execution/field-definition-1/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/execution/field-definition-1/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/execution/field-definition-1/err.expected b/integration-tests/execution/field-definition-1/err.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/field-definition-1/ghul.json b/integration-tests/execution/field-definition-1/ghul.json new file mode 100644 index 000000000..84539d122 --- /dev/null +++ b/integration-tests/execution/field-definition-1/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll", + "source": [ + "." + ] +} diff --git a/integration-tests/execution/field-definition-1/ghulflags b/integration-tests/execution/field-definition-1/ghulflags new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/field-definition-1/il.expected b/integration-tests/execution/field-definition-1/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/field-definition-1/run.expected b/integration-tests/execution/field-definition-1/run.expected new file mode 100644 index 000000000..fe922207d --- /dev/null +++ b/integration-tests/execution/field-definition-1/run.expected @@ -0,0 +1 @@ +field value is 5 diff --git a/integration-tests/execution/field-definition-1/test.ghul b/integration-tests/execution/field-definition-1/test.ghul new file mode 100644 index 000000000..1bb2490d4 --- /dev/null +++ b/integration-tests/execution/field-definition-1/test.ghul @@ -0,0 +1,17 @@ +namespace Test.PublicFields is + use Collections; + + class TEST is + value: int field; + + init(f: int) is + value = f; + si + si + + entry() is + let t = new TEST(5); + + IO.Std.write_line("field value is {t.value}"); + si +si diff --git a/integration-tests/execution/field-definition-1/warn.expected b/integration-tests/execution/field-definition-1/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/naming-convention-destructure-1/.vscode/tasks.json b/integration-tests/execution/naming-convention-destructure-1/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/execution/naming-convention-destructure-1/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/execution/naming-convention-destructure-1/err.expected b/integration-tests/execution/naming-convention-destructure-1/err.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/naming-convention-destructure-1/ghul.json b/integration-tests/execution/naming-convention-destructure-1/ghul.json new file mode 100644 index 000000000..84539d122 --- /dev/null +++ b/integration-tests/execution/naming-convention-destructure-1/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll", + "source": [ + "." + ] +} diff --git a/integration-tests/execution/naming-convention-destructure-1/ghulflags b/integration-tests/execution/naming-convention-destructure-1/ghulflags new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/naming-convention-destructure-1/il.expected b/integration-tests/execution/naming-convention-destructure-1/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/naming-convention-destructure-1/run.expected b/integration-tests/execution/naming-convention-destructure-1/run.expected new file mode 100644 index 000000000..7875cf4f4 --- /dev/null +++ b/integration-tests/execution/naming-convention-destructure-1/run.expected @@ -0,0 +1,20 @@ +blob_let_a() = 10 +blob_let_b() = 10 +blob_let_c((1, 2, 3, 4)) = 10 +blob_let_d() = 6 +blob_let_s() = a = 1, b = AA, c = 3, d = BB +blob_assign_a() = 10 +blob_assign_b() = 10 +blob_assign_c((1, 2, 3, 4)) = 10 +blob_assign_d() = 6 +blob_assign_s() = a = 1, b = AA, c = 3, d = BB +i = 0, j = 1 +i = 1, j = 2 +i = 2, j = 3 +i = 3, j = 4 +i = 4, j = 5 +i = 5, j = 6 +i = 6, j = 7 +i = 7, j = 8 +i = 8, j = 9 +i = 9, j = 10 diff --git a/integration-tests/execution/naming-convention-destructure-1/test.ghul b/integration-tests/execution/naming-convention-destructure-1/test.ghul new file mode 100644 index 000000000..820434c73 --- /dev/null +++ b/integration-tests/execution/naming-convention-destructure-1/test.ghul @@ -0,0 +1,142 @@ + +use IO.Std.write_line; + +entry() is + write_line("blob_let_a() = {blob_let_a()}"); + write_line("blob_let_b() = {blob_let_b()}"); + write_line("blob_let_c((1, 2, 3, 4)) = {blob_let_c((1, 2, 3, 4))}"); + write_line("blob_let_d() = {blob_let_d()}"); + write_line("blob_let_s() = {blob_let_s()}"); + write_line("blob_assign_a() = {blob_assign_a()}"); + write_line("blob_assign_b() = {blob_assign_b()}"); + write_line("blob_assign_c((1, 2, 3, 4)) = {blob_assign_c((1, 2, 3, 4))}"); + write_line("blob_assign_d() = {blob_assign_d()}"); + write_line("blob_assign_s() = {blob_assign_s()}"); + blob_f(); +si + +// struct with fields: +struct BLOB_4[T, U, V, W] is + `0: T field; + `1: U field; + `2: V field; + `3: W field; + + init(a: T, b: U, c: V, d: W) is + `0 = a; + `1 = b; + `2 = c; + `3 = d; + si +si + +// class with fields +class BLOB_3[T, U, V] is + `0: T field; + `1: U field; + `2: V field; + + init(a: T, b: U, c: V) is + `0 = a; + `1 = b; + `2 = c; + si +si + +// struct with properties: +struct BLOB_2[T, U] is + `0: T; + `1: U; + + init(a: T, b: U) is + `0 = a; + `1 = b; + si +si + +blob_let_a() -> int is + let t = new BLOB_4[int,int,int,int](1, 2, 3, 4); + + let (a, b, c, d) = t; + + return a + b + c + d; +si + +blob_let_b() -> int is + let (a, b, c, d) = new BLOB_4[int,int,int,int](1, 2, 3, 4); + + return a + b + c + d; +si + +blob_let_c(t: (int, int, int, int)) -> int is + let (a, b, c, d) = new BLOB_4[int,int,int,int](t.`0, t.`1, t.`2, t.`3); + + return a + b + c + d; +si + +blob_let_d() -> int is + let (a, (b, c), d) = new BLOB_3[int, BLOB_2[int, int], int](1, new BLOB_2[int, int](2, 3), 4); + + return a + b + c + d; +si + +blob_let_s() -> string is + let (a, b, c, d) = new BLOB_4[int, string, int, string](1, "AA", 3, "BB"); + + return "a = {a}, b = {b}, c = {c}, d = {d}"; +si + +blob_assign_a() -> int is + let t = new BLOB_4[int,int,int,int](1, 2, 3, 4); + + let a: int, b: int, c: int, d: int; + + (a, b, c, d) = t; + + return a + b + c + d; +si + +blob_assign_b() -> int is + let a: int, b: int, c: int, d: int; + + (a, b, c, d) = new BLOB_4[int,int,int,int](1, 2, 3, 4); + + return a + b + c + d; +si + +blob_assign_c(t: (int, int, int, int)) -> int is + let a: int, b: int, c: int, d: int; + + (a, b, c, d) = new BLOB_4[int,int,int,int](t.`0, t.`1, t.`2, t.`3); + + return a + b + c + d; +si + +blob_assign_d() -> int is + let a: int, b: int, c: int, d: int; + + (a, (b, c), d) = new BLOB_3[int, BLOB_2[int, int], int](1, new BLOB_2[int, int](2, 3), 4); + + return a + b + c + d; +si + +blob_assign_s() -> string is + let a: int, b: string, c: int, d: string; + + (a, b, c, d) = new BLOB_4[int, string, int, string](1, "AA", 3, "BB"); + + return "a = {a}, b = {b}, c = {c}, d = {d}"; +si + +blob_f() is + let a = new Collections.LIST[BLOB_2[int, int]](); + + for i in 0..10 do + // a.add((i, i + 1)); + a.add(new BLOB_2[int, int](i, i + 1)); + od + + for (i, j) in a do + write_line("i = {i}, j = {j}"); + od +si diff --git a/integration-tests/execution/naming-convention-destructure-1/warn.expected b/integration-tests/execution/naming-convention-destructure-1/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/field-definition-1/.vscode/tasks.json b/integration-tests/parse/field-definition-1/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/parse/field-definition-1/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/parse/field-definition-1/err.expected b/integration-tests/parse/field-definition-1/err.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/field-definition-1/fail.expected b/integration-tests/parse/field-definition-1/fail.expected new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/integration-tests/parse/field-definition-1/fail.expected @@ -0,0 +1 @@ + diff --git a/integration-tests/parse/field-definition-1/ghul.json b/integration-tests/parse/field-definition-1/ghul.json new file mode 100644 index 000000000..84539d122 --- /dev/null +++ b/integration-tests/parse/field-definition-1/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll", + "source": [ + "." + ] +} diff --git a/integration-tests/parse/field-definition-1/ghulflags b/integration-tests/parse/field-definition-1/ghulflags new file mode 100644 index 000000000..4d095b972 --- /dev/null +++ b/integration-tests/parse/field-definition-1/ghulflags @@ -0,0 +1 @@ +--type-check \ No newline at end of file diff --git a/integration-tests/parse/field-definition-1/il.expected b/integration-tests/parse/field-definition-1/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/parse/field-definition-1/test.ghul b/integration-tests/parse/field-definition-1/test.ghul new file mode 100644 index 000000000..1efc4159f --- /dev/null +++ b/integration-tests/parse/field-definition-1/test.ghul @@ -0,0 +1,7 @@ +namespace Test.ParseUnary is + use Collections; + + class Test is + value: int field; + si +si diff --git a/integration-tests/parse/field-definition-1/warn.expected b/integration-tests/parse/field-definition-1/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/semantic/field-definition-1/.vscode/tasks.json b/integration-tests/semantic/field-definition-1/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/semantic/field-definition-1/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run test", + "command": "dotnet ghul-test \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "Capture test expectation", + "command": "../../../tasks/capture.sh \"${workspaceFolder}\"", + "type": "shell", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/integration-tests/semantic/field-definition-1/err.expected b/integration-tests/semantic/field-definition-1/err.expected new file mode 100644 index 000000000..558545f92 --- /dev/null +++ b/integration-tests/semantic/field-definition-1/err.expected @@ -0,0 +1,3 @@ +test.ghul: 24,26..24,37: error: _value: int is not publicly readable +test.ghul: 39,9..39,19: error: value: int is not publicly assignable +test.ghul: 42,9..42,20: error: `value: int is not publicly assignable diff --git a/integration-tests/semantic/field-definition-1/fail.expected b/integration-tests/semantic/field-definition-1/fail.expected new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/integration-tests/semantic/field-definition-1/fail.expected @@ -0,0 +1 @@ + diff --git a/integration-tests/semantic/field-definition-1/ghul.json b/integration-tests/semantic/field-definition-1/ghul.json new file mode 100644 index 000000000..84539d122 --- /dev/null +++ b/integration-tests/semantic/field-definition-1/ghul.json @@ -0,0 +1,6 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll", + "source": [ + "." + ] +} diff --git a/integration-tests/semantic/field-definition-1/ghulflags b/integration-tests/semantic/field-definition-1/ghulflags new file mode 100644 index 000000000..4d095b972 --- /dev/null +++ b/integration-tests/semantic/field-definition-1/ghulflags @@ -0,0 +1 @@ +--type-check \ No newline at end of file diff --git a/integration-tests/semantic/field-definition-1/il.expected b/integration-tests/semantic/field-definition-1/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/semantic/field-definition-1/test.ghul b/integration-tests/semantic/field-definition-1/test.ghul new file mode 100644 index 000000000..3383df964 --- /dev/null +++ b/integration-tests/semantic/field-definition-1/test.ghul @@ -0,0 +1,44 @@ +namespace Test.ParseUnary is + use Collections; + + class TEST is + _value: int field; + value: int field; + `value: int field; + + init(`0: int, `1: int, `2: int) is + // all should be assignable here + _value = `0; + value = `1; + `value = `2; + si + + get_values() -> (int, int, int) => + (_value, value, `value); + si + + test_is_not_publicly_readable() is + let test = new TEST(1, 2, 3); + + // expect error + let value: int = test._value; + si + + test_is_publicly_readable() is + let test = new TEST(1, 2, 3); + + let value: int = test.value; + + value = test.`value; + si + + test_is_not_publicly_assignable() is + let test = new TEST(1, 2, 3); + + // expect error + test.value = 5; + + // expect error + test.`value = 5; + si +si diff --git a/integration-tests/semantic/field-definition-1/warn.expected b/integration-tests/semantic/field-definition-1/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/src/compiler/build_flags.ghul b/src/compiler/build_flags.ghul index c640c0df7..0513aa2a6 100644 --- a/src/compiler/build_flags.ghul +++ b/src/compiler/build_flags.ghul @@ -13,6 +13,7 @@ namespace Compiler is want_executable: bool public; want_assembler: bool public; want_keep_out_il: bool public; + want_library_boilerplate: bool public; ignore_errors: bool public; exclude_runtime_symbols: bool public; is_test_run: bool public; diff --git a/src/driver/main.ghul b/src/driver/main.ghul index b796a1276..9fdc5e0be 100644 --- a/src/driver/main.ghul +++ b/src/driver/main.ghul @@ -36,6 +36,8 @@ namespace Driver is module_version: string; assembly_info: Semantic.DotNet.ASSEMBLY_INFO; + emit_boilerplate_il: string; + compiler_version: string is let assembly_version = System.Reflection.Assembly.get_entry_assembly().get_custom_attributes(typeof System.Reflection.AssemblyInformationalVersionAttribute, false) | @@ -174,6 +176,7 @@ namespace Driver is si; let conditional_defines = new Collections.LIST[string](); + let emit_boilerplate_il_path: string; for s in args_iterator do if s =~ "-A" \/ s =~ "--analyse" then @@ -273,6 +276,8 @@ namespace Driver is flags.exclude_runtime_symbols = true; elif s =~ "--keep-out-il" then flags.want_keep_out_il = true; + elif s =~ "--emit-boilerplate-il" then + emit_boilerplate_il_path = get_next_argument(args_iterator); elif s.starts_with('-') then Std.error.write_line("warning: ignoring unknown option: {s}"); elif SOURCE_FILE_CATEGORIZER.is_ghul(s) then @@ -283,6 +288,14 @@ namespace Driver is fi od + if emit_boilerplate_il_path? then + if !File.exists(emit_boilerplate_il_path) then + Std.error.write_line("warning: library boilerplate IL file not found: {emit_boilerplate_il_path}"); + else + emit_boilerplate_il = File.read_all_text(emit_boilerplate_il_path); + fi + fi + flags.mark_valid(); if !flags.want_stubs then @@ -317,7 +330,6 @@ namespace Driver is "header-tail", [module_name] ); - fi si @@ -345,6 +357,9 @@ namespace Driver is let ir_context = container.ir_context; let output_file = output_file_name_generator.result; + if emit_boilerplate_il? then + container.ir_context.write(emit_boilerplate_il); + fi if output_file.last_index_of('.') < 0 then output_file = output_file + ".exe"; diff --git a/src/ir/values/value.ghul b/src/ir/values/value.ghul index 2b24b4532..5360de95b 100644 --- a/src/ir/values/value.ghul +++ b/src/ir/values/value.ghul @@ -71,9 +71,9 @@ namespace IR.Values is update_from(other: Value) is assert other?; - for field in self.get_type().get_fields() do - let value = field.get_value(other); - field.set_value(self, value); + for `field in self.get_type().get_fields() do + let value = `field.get_value(other); + `field.set_value(self, value); od for property in self.get_type().get_properties() | .filter(p => p.can_read /\ p.can_write) do diff --git a/src/lexical/token.ghul b/src/lexical/token.ghul index 3ba27042b..357f44400 100644 --- a/src/lexical/token.ghul +++ b/src/lexical/token.ghul @@ -30,6 +30,7 @@ namespace Lexical is ESAC, FALSE, FI, + FIELD, FINALLY, FLOAT_LITERAL, FOR, diff --git a/src/lexical/token_names.ghul b/src/lexical/token_names.ghul index a83155f78..072e86348 100644 --- a/src/lexical/token_names.ghul +++ b/src/lexical/token_names.ghul @@ -37,6 +37,7 @@ namespace Lexical is _names[TOKEN.ESAC] = "esac"; _names[TOKEN.FALSE] = "false"; _names[TOKEN.FI] = "fi"; + _names[TOKEN.FIELD] = "field"; _names[TOKEN.FINALLY] = "finally"; _names[TOKEN.FLOAT_LITERAL] = "float literal"; _names[TOKEN.FOR] = "for"; diff --git a/src/lexical/tokenizer.ghul b/src/lexical/tokenizer.ghul index 90353a767..fc74864bc 100644 --- a/src/lexical/tokenizer.ghul +++ b/src/lexical/tokenizer.ghul @@ -109,6 +109,7 @@ namespace Lexical is _symbol_tokens["public"] = TOKEN.PUBLIC; _symbol_tokens["protected"] = TOKEN.PROTECTED; _symbol_tokens["private"] = TOKEN.PRIVATE; + _symbol_tokens["field"] = TOKEN.FIELD; _symbol_tokens["const"] = TOKEN.CONST; _symbol_tokens["static"] = TOKEN.STATIC; _symbol_tokens["innate"] = TOKEN.INNATE; diff --git a/src/semantic/dotnet/symbol_factory.ghul b/src/semantic/dotnet/symbol_factory.ghul index 96bf48144..4910025c3 100644 --- a/src/semantic/dotnet/symbol_factory.ghul +++ b/src/semantic/dotnet/symbol_factory.ghul @@ -336,18 +336,18 @@ namespace Semantic.DotNet is owner.add_member(result); si - add_field(owner: Symbols.Scoped, type: TYPE, field: FieldInfo) is - if !field.is_public then + add_field(owner: Symbols.Scoped, type: TYPE, `field: FieldInfo) is + if !`field.is_public then return; fi - if type.is_enum /\ !field.is_special_name then + if type.is_enum /\ !`field.is_special_name then let enum_member = new Symbols.ENUM_STRUCT_MEMBER( Source.LOCATION.reflected, cast Symbols.ENUM_STRUCT(owner), - _type_name_map.get_constant_name(owner.qualified_name, field.name, false), - field.get_raw_constant_value().to_string() + _type_name_map.get_constant_name(owner.qualified_name, `field.name, false), + `field.get_raw_constant_value().to_string() ); owner.add_member(enum_member); @@ -358,11 +358,11 @@ namespace Semantic.DotNet is let result: Symbols.Field; let location = Source.LOCATION.reflected; - let field_name = field.name; + let field_name = `field.name; let name: string; - if field_name.length == 5 /\ field.name.starts_with("Item") /\ type.is_generic_type then + if field_name.length == 5 /\ `field.name.starts_with("Item") /\ type.is_generic_type then let unspecialized_type = type.get_generic_type_definition(); let is_tuple = false; @@ -380,10 +380,20 @@ namespace Semantic.DotNet is fi if !name? then - name = _type_name_map.get_member_name(owner.qualified_name, field.name, false, false); + name = _type_name_map.get_member_name(owner.qualified_name, `field.name, false, false); fi - if field.is_static then + try + let attributes = `field.get_custom_attributes_data(); + + for a in attributes do + debug_always("field {`field.name} attribute: {a} of type {a.attribute_type}") + od + catch ex: System.Exception + debug_always("field {`field.name} attribute exception: {ex}"); + yrt + + if `field.is_static then result = new Symbols.STATIC_FIELD(location, owner, name); elif type.is_value_type then result = new Symbols.STRUCT_FIELD(location, owner, name); @@ -391,9 +401,9 @@ namespace Semantic.DotNet is result = new Symbols.INSTANCE_FIELD(location, owner, name); fi - result.il_name_override = field.name; + result.il_name_override = `field.name; - result.set_type(_type_mapper.get_type(field.field_type)); + result.set_type(_type_mapper.get_type(`field.field_type)); owner.add_member(result); si diff --git a/src/semantic/dotnet/tuple_type_wrappers.ghul b/src/semantic/dotnet/tuple_type_wrappers.ghul index 577196ea4..d0d621fcc 100644 --- a/src/semantic/dotnet/tuple_type_wrappers.ghul +++ b/src/semantic/dotnet/tuple_type_wrappers.ghul @@ -10,7 +10,7 @@ namespace Semantic.DotNet is class TUPLE_TYPE_WRAPPER: GENERIC_TYPE_WRAPPER is names: Collections.List[string]; - is_destructurable: bool => true; + is_value_tuple: bool => true; short_description: string => Types.TUPLE.get_short_description(self, names); @@ -36,7 +36,7 @@ namespace Semantic.DotNet is class HYBRID_TUPLE_TYPE_WRAPPER: HYBRID_GENERIC_TYPE_WRAPPER is names: Collections.List[string]; - is_destructurable: bool => true; + is_value_tuple: bool => true; short_description: string => Types.TUPLE.get_short_description(self, names); diff --git a/src/semantic/symbols/classy.ghul b/src/semantic/symbols/classy.ghul index c5e18284f..23c675a64 100644 --- a/src/semantic/symbols/classy.ghul +++ b/src/semantic/symbols/classy.ghul @@ -790,9 +790,9 @@ namespace Semantic.Symbols is let argument_names = new Collections.LIST[string](); let arguments = new Collections.LIST[Type](); - for field in symbols | .filter(s => s.is_variable).map(s => cast Variable(s)) do - argument_names.add(field.name); - arguments.add(field.type); + for `field in symbols | .filter(s => s.is_variable).map(s => cast Variable(s)) do + argument_names.add(`field.name); + arguments.add(`field.type); od constructor.set_arguments(argument_names, arguments); @@ -813,17 +813,17 @@ namespace Semantic.Symbols is si declare_captured(name: string, type: Type, symbol_definition_listener: SymbolDefinitionListener) -> Field is - let field = new Symbols.INSTANCE_FIELD(LOCATION.internal, self, name); + let `field = new Symbols.INSTANCE_FIELD(LOCATION.internal, self, name); - declare(location, field, symbol_definition_listener); + declare(location, `field, symbol_definition_listener); - field.il_name_override = field.il_name_override; + `field.il_name_override = `field.il_name_override; - field.set_type(type); + `field.set_type(type); - _captures.add(field); + _captures.add(`field); - return field; + return `field; si gen_all(context: IR.CONTEXT, symbol_loader: SYMBOL_LOADER) is diff --git a/src/semantic/symbols/closure.ghul b/src/semantic/symbols/closure.ghul index ed31fe8f4..dfee9a51f 100644 --- a/src/semantic/symbols/closure.ghul +++ b/src/semantic/symbols/closure.ghul @@ -174,9 +174,9 @@ namespace Semantic.Symbols is return new IR.Values.Load.SELF(context, context.type); fi - let field = find_or_add_capture_self(); + let `field = find_or_add_capture_self(); - return loader.load_instance_variable(location, new IR.Values.Load.SELF(frame, frame.type), field); + return loader.load_instance_variable(location, new IR.Values.Load.SELF(frame, frame.type), `field); si load_outer_captured_value(location: LOCATION, symbol: Variable, loader: SYMBOL_LOADER) -> Value is @@ -223,9 +223,9 @@ namespace Semantic.Symbols is load_captured_value(location: LOCATION, symbol: Variable, loader: SYMBOL_LOADER) -> Value is symbol.is_captured = true; - let field = find_or_add_capture(symbol); + let `field = find_or_add_capture(symbol); - return loader.load_instance_variable(LOCATION.internal, new IR.Values.Load.SELF(frame, frame.type), field); + return loader.load_instance_variable(LOCATION.internal, new IR.Values.Load.SELF(frame, frame.type), `field); si gen_access(buffer: StringBuilder) is diff --git a/src/semantic/symbols/variable.ghul b/src/semantic/symbols/variable.ghul index 9d70cae40..11c60f7f3 100644 --- a/src/semantic/symbols/variable.ghul +++ b/src/semantic/symbols/variable.ghul @@ -218,7 +218,7 @@ namespace Semantic.Symbols is is_private: bool; is_field: bool => true; is_public_readable: bool => !is_private; - is_workspace_visible: bool => name? /\ !name.starts_with('_'); + is_workspace_visible: bool => !is_private; init(location: LOCATION, owner: Scope, name: string) is super.init(location, owner, name); @@ -248,9 +248,11 @@ namespace Semantic.Symbols is si gen_access(buffer: StringBuilder) is - // TODO: ideally would be 'family' as all fields are protected access, but closure frames need to be able to access - // static fields as if from the owning class: - buffer.append("assembly "); + if is_private then + buffer.append("assembly "); + else + buffer.append("public "); + fi si gen_directive(buffer: StringBuilder) is @@ -326,7 +328,6 @@ namespace Semantic.Symbols is si si - class STATIC_FIELD: Field is description: string => "{qualified_name}: {type} // class field"; diff --git a/src/semantic/types/type.ghul b/src/semantic/types/type.ghul index fc630fc40..deb8df543 100644 --- a/src/semantic/types/type.ghul +++ b/src/semantic/types/type.ghul @@ -68,7 +68,7 @@ namespace Semantic.Types is is_function: bool => false; is_function_with_any_implicit_argument_types: bool => false; is_ref: bool => false; // specifically 'ref', not just a reference type - is_destructurable: bool => false; + is_value_tuple: bool => false; init() is si diff --git a/src/syntax/parsers/modifiers/modifier.ghul b/src/syntax/parsers/modifiers/modifier.ghul index 572e45a6e..3351548df 100644 --- a/src/syntax/parsers/modifiers/modifier.ghul +++ b/src/syntax/parsers/modifiers/modifier.ghul @@ -31,6 +31,11 @@ namespace Syntax.Parsers.Modifiers is (context: CONTEXT) => new Trees.Modifiers.CONST(context.location_and_next()), Lexical.TOKEN.CONST ); + + add_parser( + (context: CONTEXT) => new Trees.Modifiers.FIELD(context.location_and_next()), + Lexical.TOKEN.FIELD + ); si other_token(context: CONTEXT) -> Trees.Modifiers.Modifier is diff --git a/src/syntax/process/add_accessors_for_properties.ghul b/src/syntax/process/add_accessors_for_properties.ghul index b4b55da38..4aad1a208 100644 --- a/src/syntax/process/add_accessors_for_properties.ghul +++ b/src/syntax/process/add_accessors_for_properties.ghul @@ -52,6 +52,20 @@ namespace Syntax.Process is si pre(property: Definitions.PROPERTY) -> bool is + if property.modifiers.is_field then + if property.read_body? \/ property.assign_argument? then + IoC.CONTAINER.instance.logger + .error(property.read_body.location, "fields cannot have accessors"); + fi + + if property.name.name.starts_with('_') then + IoC.CONTAINER.instance.logger + .info(property.read_body.location, "unnecessary field modifier"); + fi + + return true; + fi + if !property.is_poisoned /\ ( !property.name.name.starts_with('_') \/ diff --git a/src/syntax/process/compile_expressions.ghul b/src/syntax/process/compile_expressions.ghul index eb3ec2523..48ca39706 100644 --- a/src/syntax/process/compile_expressions.ghul +++ b/src/syntax/process/compile_expressions.ghul @@ -305,6 +305,29 @@ namespace Syntax.Process is left.value = left.expression.value; si + get_conventionally_named_destructure_member_types(type: Type) -> Collections.List[Type] is + // look for elements named _0, _1, _2, etc. + // TODO proper deconstruct(...) support + + let result = new Collections.LIST[Type](); + + let i = 0; + + do + let member = type.find_member("`{i}"); + + if !member? then + break; + fi + + result.add(member.type); + + i = i + 1; + od + + return result; + si + pre(left: Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION) -> bool is super.pre(left); @@ -320,13 +343,19 @@ namespace Syntax.Process is return true; fi - if !from_type.is_destructurable then + let from_types: Collections.List[Type]; + + if from_type.is_value_tuple then + from_types = from_type.arguments; + else + from_types = get_conventionally_named_destructure_member_types(from_type); + fi + + if from_types.count == 0 then _logger.error(left.location, "cannot destructure {from_type}"); return true; fi - // TODO deconstruct(...) support - let from_types = from_type.arguments; let elements = left.elements; let count = elements.count; @@ -1611,7 +1640,7 @@ namespace Syntax.Process is si check_store_access(location: LOCATION, from: Value, symbol: Semantic.Symbols.Symbol, type: Type) -> bool is - if !symbol.is_private then + if !symbol.is_private /\ !symbol.is_field then return true; fi @@ -1746,13 +1775,19 @@ namespace Syntax.Process is _logger.error(left.location, "oops: null type"); fi - if !from_type.is_destructurable then + let from_types: Collections.List[Type]; + + if from_type.is_value_tuple then + from_types = from_type.arguments; + else + from_types = get_conventionally_named_destructure_member_types(from_type); + fi + + if from_types.count == 0 then _logger.error(left.location, "cannot destructure {from_type}"); return true; fi - // TODO deconstruct(...) support - let from_types = from_type.arguments; let elements = left.elements; let count = elements.count; @@ -1862,30 +1897,40 @@ namespace Syntax.Process is fi si - set_symbol_destructure_types(left: Trees.Variables.VariableLeft, type: Type) is - if !type? \/ !type.is_destructurable then - _logger.error(left.location, "cannot destructure {type}"); + // TODO used by for loop - needs removing + set_symbol_destructure_types(left: Trees.Variables.VariableLeft, from_type: Type) is + if !from_type? then return; fi - // TODO this is safe while we only support tuples - let types = type.arguments; + let from_types: Collections.List[Type]; + + if from_type.is_value_tuple then + from_types = from_type.arguments; + else + from_types = get_conventionally_named_destructure_member_types(from_type); + fi + + if from_types.count == 0 then + _logger.error(left.location, "cannot destructure {from_type}"); + return; + fi let elements = left.elements; let count = elements.count; - if elements.count != types.count then - _logger.error(left.location, "expected {elements.count} destructuring elements but found {types.count}"); + if elements.count != from_types.count then + _logger.error(left.location, "expected {elements.count} destructuring elements but found {from_types.count}"); - if elements.count > types.count then - count = types.count; + if elements.count > from_types.count then + count = from_types.count; fi fi for i in 0..count do let element = elements[i]; - let type = types[i]; + let type = from_types[i]; if !element? then return; @@ -1907,10 +1952,11 @@ namespace Syntax.Process is od si + // TODO used by for loop - needs removing get_destructure_types(type: Type) -> Collections.List[Type] is let result = new Collections.LIST[Type](); - if !type? \/ !type.is_destructurable then + if !type? \/ !type.is_value_tuple then debug_always("## get_destructure_types: not destructurable {type}"); return result; @@ -1921,11 +1967,12 @@ namespace Syntax.Process is return result; si + // TODO used by for loop - needs removing get_destructure_types_into(type: Type, into: Collections.MutableList[Type]) is let result = new Collections.LIST[Type](); for t in type.arguments do - if t.is_destructurable then + if t.is_value_tuple then get_destructure_types_into(t, into); else into.add(t); diff --git a/src/syntax/process/declare_symbols.ghul b/src/syntax/process/declare_symbols.ghul index 1c4615060..eb8197d68 100644 --- a/src/syntax/process/declare_symbols.ghul +++ b/src/syntax/process/declare_symbols.ghul @@ -448,9 +448,11 @@ namespace Syntax.Process is let is_static = property.modifiers.is_static; if - property.name.name.starts_with('_') /\ - !property.read_body? /\ - !property.assign_body? + property.modifiers.is_field \/ ( + property.name.name.starts_with('_') /\ + !property.read_body? /\ + !property.assign_body? + ) then let symbol = current_declaration_context.declare_variable(property.name.location, property.name.name, is_static, _symbol_definition_listener); diff --git a/src/syntax/process/generate_il.ghul b/src/syntax/process/generate_il.ghul index a5ce4ca24..09a882a1f 100644 --- a/src/syntax/process/generate_il.ghul +++ b/src/syntax/process/generate_il.ghul @@ -609,6 +609,7 @@ namespace Syntax.Process is gen_destructuring_initialize(v.left, v.initializer.value); si + // TODO use Pre/Visit instead gen_destructuring_initialize(left: Variables.VariableLeft, value: Value) is if left.is_simple_name then let symbol = find(left.name); @@ -630,7 +631,7 @@ namespace Syntax.Process is let temp = new TEMP(_context, "destructure", value); () => temp.load(); fi; - + for ei in left.elements | .index() do let i = ei.index; let e = ei.value; diff --git a/src/syntax/trees/modifiers/list.ghul b/src/syntax/trees/modifiers/list.ghul index a975a3e8e..b2f7145bc 100644 --- a/src/syntax/trees/modifiers/list.ghul +++ b/src/syntax/trees/modifiers/list.ghul @@ -7,6 +7,8 @@ namespace Syntax.Trees.Modifiers is is_empty: bool => !(access_modifier?) /\ !(storage_class?); + is_field: bool => storage_class? /\ isa FIELD(storage_class); + is_static: bool => storage_class? /\ isa STATIC(storage_class); is_public: bool => access_modifier? /\ isa PUBLIC(access_modifier); diff --git a/src/syntax/trees/modifiers/modifier.ghul b/src/syntax/trees/modifiers/modifier.ghul index e057beacc..4a5097158 100644 --- a/src/syntax/trees/modifiers/modifier.ghul +++ b/src/syntax/trees/modifiers/modifier.ghul @@ -76,4 +76,12 @@ namespace Syntax.Trees.Modifiers is super.init(location); si si + + class FIELD: StorageClass is + name: string => "field"; + + init(location: LOCATION) is + super.init(location); + si + si si