From cda02e6fdfdda6a5c90240fdf016589ae661510c Mon Sep 17 00:00:00 2001 From: degory Date: Thu, 30 May 2024 15:22:32 +0200 Subject: [PATCH] Casting improvements (#1179) Enhancements: - Consistently fold nulls and type mismatches to default values when casting to value types - Consistently fold nulls and type mismatches to default values when casting to or from generic argument types --- .config/dotnet-tools.json | 4 +- Directory.Build.props | 2 +- Directory.Packages.props | 2 +- .../.vscode/tasks.json | 23 ++ .../cast-generic-argument-type/err.expected | 0 .../cast-generic-argument-type/ghul.json | 3 + .../cast-generic-argument-type/ghulflags | 0 .../cast-generic-argument-type/il.expected | 0 .../cast-generic-argument-type/run.expected | 39 ++++ .../cast-generic-argument-type/test.ghul | 79 +++++++ .../cast-generic-argument-type/warn.expected | 0 .../il/convert-object-to-struct/ghul.json | 5 +- .../il/convert-object-to-struct/il.expected | 16 +- .../il/convert-object-to-struct/test.ghul | 5 +- .../il/convert-object-to-struct/test.ghulproj | 15 -- src/ioc/container.ghul | 13 ++ src/ir/block_context.ghul | 6 +- src/ir/block_stack.ghul | 16 +- src/ir/temp.ghul | 10 +- src/ir/values/default.ghul | 39 ++++ src/ir/values/type_wrapper.ghul | 1 + src/ir/values/wrapper.ghul | 1 - src/semantic/symbols/generic_argument.ghul | 3 + src/semantic/type_caster.ghul | 196 ++++++++++++++++++ src/syntax/process/compile_expressions.ghul | 65 ++---- src/syntax/process/generate_il.ghul | 18 +- 26 files changed, 467 insertions(+), 94 deletions(-) create mode 100644 integration-tests/execution/cast-generic-argument-type/.vscode/tasks.json create mode 100644 integration-tests/execution/cast-generic-argument-type/err.expected create mode 100644 integration-tests/execution/cast-generic-argument-type/ghul.json create mode 100644 integration-tests/execution/cast-generic-argument-type/ghulflags create mode 100644 integration-tests/execution/cast-generic-argument-type/il.expected create mode 100644 integration-tests/execution/cast-generic-argument-type/run.expected create mode 100644 integration-tests/execution/cast-generic-argument-type/test.ghul create mode 100644 integration-tests/execution/cast-generic-argument-type/warn.expected delete mode 100644 integration-tests/il/convert-object-to-struct/test.ghulproj create mode 100644 src/ir/values/default.ghul create mode 100644 src/semantic/type_caster.ghul diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 72e60e974..bb82a85fa 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -9,13 +9,13 @@ ] }, "ghul.test": { - "version": "1.3.3", + "version": "1.3.5", "commands": [ "ghul-test" ] }, "ghul.compiler": { - "version": "0.8.46", + "version": "0.8.47", "commands": [ "ghul-compiler" ] diff --git a/Directory.Build.props b/Directory.Build.props index 7318ae88c..06ce6ec43 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 0.8.47-alpha.5 + 0.8.48-alpha.26 $(NoWarn);NU1507 diff --git a/Directory.Packages.props b/Directory.Packages.props index 0a0e1fd55..9d0ace397 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - + diff --git a/integration-tests/execution/cast-generic-argument-type/.vscode/tasks.json b/integration-tests/execution/cast-generic-argument-type/.vscode/tasks.json new file mode 100644 index 000000000..49063613c --- /dev/null +++ b/integration-tests/execution/cast-generic-argument-type/.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/cast-generic-argument-type/err.expected b/integration-tests/execution/cast-generic-argument-type/err.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/cast-generic-argument-type/ghul.json b/integration-tests/execution/cast-generic-argument-type/ghul.json new file mode 100644 index 000000000..ddcfcf2b1 --- /dev/null +++ b/integration-tests/execution/cast-generic-argument-type/ghul.json @@ -0,0 +1,3 @@ +{ + "compiler": "dotnet ../../../publish/ghul.dll" +} diff --git a/integration-tests/execution/cast-generic-argument-type/ghulflags b/integration-tests/execution/cast-generic-argument-type/ghulflags new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/cast-generic-argument-type/il.expected b/integration-tests/execution/cast-generic-argument-type/il.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/execution/cast-generic-argument-type/run.expected b/integration-tests/execution/cast-generic-argument-type/run.expected new file mode 100644 index 000000000..071bf7a42 --- /dev/null +++ b/integration-tests/execution/cast-generic-argument-type/run.expected @@ -0,0 +1,39 @@ +cts object null +cts array 1 2 3 +cts 123 +cts object o hello +cts object p +cts thing +cti object null 0 +cti array 1 2 3 0 +cti 'hello' 0 +cti object o 0 +cti object p 123 +cti thing 0 +ctt object null THING #0 default +ctt array 1 2 3 THING #0 default +ctt 'hello' THING #0 default +ctt object o THING #0 default +ctt object p THING #0 default +ctt thing THING #2 initialized +cfo string object null +cfo string array 1 2 3 +cfo string 123 +cfo string object o hello +cfo string object p +cfo string thing +cfo int object null 0 +cfo int array 1 2 3 0 +cfo int 'hello' 0 +cfo int object o 0 +cfo int object p 123 +cfo int thing 0 +cfo THING object null THING #0 default +cfo THING array 1 2 3 THING #0 default +cfo THING 'hello' THING #0 default +cfo THING object o THING #0 default +cfo THING object p THING #0 default +cfo THING thing THING #5 initialized +cfs object hello hello +cfs int hello 0 +cfs THING hello THING #0 default diff --git a/integration-tests/execution/cast-generic-argument-type/test.ghul b/integration-tests/execution/cast-generic-argument-type/test.ghul new file mode 100644 index 000000000..568bee168 --- /dev/null +++ b/integration-tests/execution/cast-generic-argument-type/test.ghul @@ -0,0 +1,79 @@ +use IO.Std.write_line; + +entry() is + let n: object = null; + let a = [1, 2, 3]; + + let o: object = "hello"; + let p: object = 123; + + write_line("cts object null {cast_to_string(n)}"); + write_line("cts array 1 2 3 {cast_to_string(a)}"); + write_line("cts 123 {cast_to_string(123)}"); + write_line("cts object o {cast_to_string(o)}"); + write_line("cts object p {cast_to_string(p)}"); + write_line("cts thing {cast_to_string(THING())}"); + + write_line("cti object null {cast_to_int(n)}"); + write_line("cti array 1 2 3 {cast_to_int(a)}"); + write_line("cti 'hello' {cast_to_int("hello")}"); + write_line("cti object o {cast_to_int(o)}"); + write_line("cti object p {cast_to_int(p)}"); + write_line("cti thing {cast_to_int(THING())}"); + + write_line("ctt object null {cast_to_thing(n)}"); + write_line("ctt array 1 2 3 {cast_to_thing(a)}"); + write_line("ctt 'hello' {cast_to_thing("hello")}"); + write_line("ctt object o {cast_to_thing(o)}"); + write_line("ctt object p {cast_to_thing(p)}"); + write_line("ctt thing {cast_to_thing(THING())}"); + + write_line("cfo string object null {cast_from_object[string](null)}"); + write_line("cfo string array 1 2 3 {cast_from_object[string](a)}"); + write_line("cfo string 123 {cast_from_object[string](123)}"); + write_line("cfo string object o {cast_from_object[string](o)}"); + write_line("cfo string object p {cast_from_object[string](p)}"); + write_line("cfo string thing {cast_from_object[string](THING())}"); + + write_line("cfo int object null {cast_from_object[int](null)}"); + write_line("cfo int array 1 2 3 {cast_from_object[int](a)}"); + write_line("cfo int 'hello' {cast_from_object[int]("hello")}"); + write_line("cfo int object o {cast_from_object[int](o)}"); + write_line("cfo int object p {cast_from_object[int](p)}"); + write_line("cfo int thing {cast_from_object[int](THING())}"); + + write_line("cfo THING object null {cast_from_object[THING](null)}"); + write_line("cfo THING array 1 2 3 {cast_from_object[THING](a)}"); + write_line("cfo THING 'hello' {cast_from_object[THING]("hello")}"); + write_line("cfo THING object o {cast_from_object[THING](o)}"); + write_line("cfo THING object p {cast_from_object[THING](p)}"); + write_line("cfo THING thing {cast_from_object[THING](THING())}"); + + // this is maybe not very useful, but is orthogonal + write_line("cfs object hello {cast_from_string[object]("hello")}"); + write_line("cfs int hello {cast_from_string[int]("hello")}"); + write_line("cfs THING hello {cast_from_string[THING]("hello")}"); +si + +struct THING is + _not_default: bool; + _next: int static; + + value: int; + + init() is + value = _next; + _next = _next + 1; + _not_default = true; + si + + to_string() -> string => + "THING #{value} {if _not_default then "initialized" else "default" fi}"; +si + +cast_to_string[T](value: T) -> string => cast string(value); +cast_to_int[T](value: T) -> int => cast int(value); +cast_to_thing[T](value: T) -> THING => cast THING(value); + +cast_from_object[T](value: object) -> T => cast T(value); +cast_from_string[T](value: string) -> T => cast T(value); \ No newline at end of file diff --git a/integration-tests/execution/cast-generic-argument-type/warn.expected b/integration-tests/execution/cast-generic-argument-type/warn.expected new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/il/convert-object-to-struct/ghul.json b/integration-tests/il/convert-object-to-struct/ghul.json index 84539d122..ddcfcf2b1 100644 --- a/integration-tests/il/convert-object-to-struct/ghul.json +++ b/integration-tests/il/convert-object-to-struct/ghul.json @@ -1,6 +1,3 @@ { - "compiler": "dotnet ../../../publish/ghul.dll", - "source": [ - "." - ] + "compiler": "dotnet ../../../publish/ghul.dll" } diff --git a/integration-tests/il/convert-object-to-struct/il.expected b/integration-tests/il/convert-object-to-struct/il.expected index 7174731cb..7cf174b77 100644 --- a/integration-tests/il/convert-object-to-struct/il.expected +++ b/integration-tests/il/convert-object-to-struct/il.expected @@ -1,4 +1,14 @@ +.locals init (class ['System.Runtime']'System'.'Object' '.cast.0') ldloc 'o.1' -unbox.any class ['System.Runtime']'System'.'Object' -box valuetype 'convert_object_to_struct__test'.'STRUCT_THINGER' -stloc 't.2' \ No newline at end of file +isinst valuetype 'convert_object_to_struct__test'.'STRUCT_THINGER' +stloc '.cast.0' +ldloc '.cast.0' +brfalse L_0 +ldloc '.cast.0' +unbox.any valuetype 'convert_object_to_struct__test'.'STRUCT_THINGER' +br L_1 +L_0: +.locals init (valuetype 'convert_object_to_struct__test'.'STRUCT_THINGER' '.default.1') +ldloc '.default.1' +L_1: +stloc 't.2' diff --git a/integration-tests/il/convert-object-to-struct/test.ghul b/integration-tests/il/convert-object-to-struct/test.ghul index b5593b73c..21ee96f63 100644 --- a/integration-tests/il/convert-object-to-struct/test.ghul +++ b/integration-tests/il/convert-object-to-struct/test.ghul @@ -22,11 +22,11 @@ struct STRUCT_THINGER: GetAThing is to_string() -> string => name; si -test_convert_of_object_to_class() is +test_convert_of_object_to_struct() is let s = STRUCT_THINGER("hello"); let o: object; - let t: GetAThing; + let t: STRUCT_THINGER; o = s; @@ -36,4 +36,3 @@ test_convert_of_object_to_class() is write_line("result is: " + t.get_a_thing()); si - diff --git a/integration-tests/il/convert-object-to-struct/test.ghulproj b/integration-tests/il/convert-object-to-struct/test.ghulproj deleted file mode 100644 index 86f6a24ab..000000000 --- a/integration-tests/il/convert-object-to-struct/test.ghulproj +++ /dev/null @@ -1,15 +0,0 @@ - - - Exe - net6.0 - - dotnet ghul-compiler - - - - - - - - - diff --git a/src/ioc/container.ghul b/src/ioc/container.ghul index e514ecbe3..d761ceea1 100644 --- a/src/ioc/container.ghul +++ b/src/ioc/container.ghul @@ -84,6 +84,7 @@ namespace IoC is innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup; function_caller: Semantic.FUNCTION_CALLER; overload_resolver: Semantic.OVERLOAD_RESOLVER; + type_caster: Semantic.TYPE_CASTER; symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS; symbol_definition_locations: Semantic.SYMBOL_DEFINITION_LOCATIONS; @@ -448,6 +449,16 @@ namespace IoC is overload_resolver = Semantic.OVERLOAD_RESOLVER(logger); + type_caster = + Semantic.TYPE_CASTER( + brancher, + block_stack, + value_boxer, + value_converter, + innate_symbol_lookup, + logger + ); + symbol_use_locations = Semantic.SYMBOL_USE_LOCATIONS(); symbol_definition_locations = Semantic.SYMBOL_DEFINITION_LOCATIONS(symbol_use_locations); @@ -499,6 +510,7 @@ namespace IoC is symbol_loader, innate_symbol_lookup, function_caller, + type_caster, overload_resolver, symbol_use_locations, ir_context, @@ -531,6 +543,7 @@ namespace IoC is symbol_loader, innate_symbol_lookup, function_caller, + type_caster, overload_resolver, symbol_use_locations, ir_context, diff --git a/src/ir/block_context.ghul b/src/ir/block_context.ghul index cc0be0a79..75d14947f 100644 --- a/src/ir/block_context.ghul +++ b/src/ir/block_context.ghul @@ -3,9 +3,9 @@ namespace IR is is_in_block: bool; current_block: Values.BLOCK; - enter_block(); - enter_block(type: Semantic.Types.Type); - enter_block(block: Values.BLOCK); + enter_block() -> IR.Values.BLOCK; + enter_block(type: Semantic.Types.Type) -> IR.Values.BLOCK; + enter_block(block: Values.BLOCK) -> IR.Values.BLOCK; leave_block(); diff --git a/src/ir/block_stack.ghul b/src/ir/block_stack.ghul index fbbe2a373..b2b718a10 100644 --- a/src/ir/block_stack.ghul +++ b/src/ir/block_stack.ghul @@ -24,16 +24,22 @@ namespace IR is return _blocks.peek(); si - enter_block(block: Values.BLOCK) is + enter_block(block: Values.BLOCK) -> Values.BLOCK is _blocks.push(block); + + return block; si - enter_block(type: Semantic.Types.Type) is - enter_block(Values.BLOCK(type)); + enter_block(type: Semantic.Types.Type) -> Values.BLOCK is + let result = Values.BLOCK(type); + enter_block(result); + return result; si - enter_block() is - enter_block(Values.BLOCK()); + enter_block() -> Values.BLOCK is + let result = Values.BLOCK(); + enter_block(result); + return result; si leave_block() is diff --git a/src/ir/temp.ghul b/src/ir/temp.ghul index 49b96a0b9..e9de5a667 100644 --- a/src/ir/temp.ghul +++ b/src/ir/temp.ghul @@ -25,9 +25,7 @@ namespace IR is self.prefix = prefix; self.suffix = suffix; - self.id = _next_id; - _next_id = _next_id + 1; - + self.id = get_next_id(); self.type = type; declare(); @@ -51,6 +49,12 @@ namespace IR is _next_id = 0; si + get_next_id() -> int static is + let result = _next_id; + _next_id = _next_id + 1; + return result; + si + declare() is let buffer = System.Text.StringBuilder(); diff --git a/src/ir/values/default.ghul b/src/ir/values/default.ghul new file mode 100644 index 000000000..3812629f0 --- /dev/null +++ b/src/ir/values/default.ghul @@ -0,0 +1,39 @@ +namespace IR.Values is + use Semantic.Types.Type; + + class DEFAULT: Value is + has_type: bool => type?; + type: Type; + is_value_type: bool => type.is_value_type; + is_value_tuple: bool => type.is_value_tuple; + + has_address: bool => true; + + init( + type: Type + ) is + super.init(); + + assert type? else "type is null"; + + self.type = type; + si + + gen(context: IR.CONTEXT) is + let id = TEMP.get_next_id(); + + context.write_line(".locals init ({type.get_il_type()} '.default.{id}')"); + context.write_line("ldloc '.default.{id}'"); + si + + gen_address(context: IR.CONTEXT) is + let id = TEMP.get_next_id(); + + context.write_line(".locals init ({type.get_il_type()} '.default.{id}')"); + context.write_line("ldloca '.default.{id}'"); + si + + to_string() -> string => + "default:[{type}]()"; + si +si \ No newline at end of file diff --git a/src/ir/values/type_wrapper.ghul b/src/ir/values/type_wrapper.ghul index 5eb656b0f..0409eddf8 100644 --- a/src/ir/values/type_wrapper.ghul +++ b/src/ir/values/type_wrapper.ghul @@ -4,6 +4,7 @@ namespace IR.Values is class TYPE_WRAPPER: Value is has_type: bool => type?; has_address: bool => value.has_address; + is_lightweight_pure: bool => value.is_lightweight_pure; type: Type; value: Value; diff --git a/src/ir/values/wrapper.ghul b/src/ir/values/wrapper.ghul index 35b859d37..1eba2f182 100644 --- a/src/ir/values/wrapper.ghul +++ b/src/ir/values/wrapper.ghul @@ -8,7 +8,6 @@ namespace IR.Values is value: Value public; init( - type: Type, value: Value ) is super.init(); diff --git a/src/semantic/symbols/generic_argument.ghul b/src/semantic/symbols/generic_argument.ghul index 25bdde661..4a8da7ef3 100644 --- a/src/semantic/symbols/generic_argument.ghul +++ b/src/semantic/symbols/generic_argument.ghul @@ -13,7 +13,10 @@ namespace Semantic.Symbols is type: Type; set_type(value: Type) is type = value; si + // NOTE this is neccessary, because values of generic argument types + // need to be boxed before they can be treated as instances of System.Object is_value_type: bool => true; + is_type: bool => true; is_type_variable: bool => true; diff --git a/src/semantic/type_caster.ghul b/src/semantic/type_caster.ghul new file mode 100644 index 000000000..77c0587de --- /dev/null +++ b/src/semantic/type_caster.ghul @@ -0,0 +1,196 @@ +namespace Semantic is + use IO.Std; + + use Logging; + + use Types.Type; + + use IR.Values; + + use IR.TEMP; + use IR.BRANCH; + + class TYPE_CASTER is + _brancher: IR.BRANCHER; + _block_context: IR.BlockContext; + _value_boxer: IR.VALUE_BOXER; + _value_converter: IR.VALUE_CONVERTER; + _innate_symbol_lookup: Lookups.InnateSymbolLookup; + _logger: Logger; + + init( + brancher: IR.BRANCHER, + block_context: IR.BlockContext, + value_boxer: IR.VALUE_BOXER, + value_converter: IR.VALUE_CONVERTER, + innate_symbol_lookup: Lookups.InnateSymbolLookup, + logger: Logger + ) + is + super.init(); + + _brancher = brancher; + _block_context = block_context; + _value_boxer = value_boxer; + _value_converter = value_converter; + _innate_symbol_lookup = innate_symbol_lookup; + _logger = logger; + si + + check_cast_is_valid(location: Source.LOCATION, source_type: Type, target_type: Type) -> bool is + if source_type.is_type_variable /\ target_type.is_type_variable then + _logger.error(location, "cannot convert {source_type} to {target_type}"); + + return false; + fi + + if source_type.is_type_variable \/ target_type.is_type_variable then + return true; + fi + + if source_type.is_value_type /\ target_type.is_value_type then + let instruction = _value_converter.get_instruction(target_type); + + if !instruction? then + let enum_type = _innate_symbol_lookup.get_enum_type(); + + if + !enum_type.is_assignable_from(source_type) /\ + !enum_type.is_assignable_from(target_type) + then + _logger.error(location, "cannot convert {source_type} to {target_type}"); + return false; + fi + fi + fi + + return true; + si + + cast_value( + location: Source.LOCATION, + value: IR.Values.Value, + target_type: Type, + quiet: bool + ) -> IR.Values.Value is + let need_conditional_unbox = false; + + if value.type.is_type_variable /\ target_type.is_type_variable then + if !quiet then + _logger.error(location, "cannot convert {value.type} to {target_type}"); + fi + + return + DUMMY( + target_type, + location + ); + fi + + if value.type.is_type_variable \/ target_type.is_type_variable then + let result: IR.Values.Value = + CAST( + target_type, + value + ); + + if target_type.is_value_type then + result = _conditionally_unbox(result, target_type); + fi + + return result; + fi + + if value.type.is_value_type /\ target_type.is_value_type then + let instruction = _value_converter.get_instruction(target_type); + + if !instruction? then + let enum_type = _innate_symbol_lookup.get_enum_type(); + + if + !enum_type.is_assignable_from(value.type) /\ + !enum_type.is_assignable_from(target_type) + then + if !quiet then + _logger.error(location, "cannot convert {value.type} to {target_type}"); + fi + + return + DUMMY( + target_type, + location + ); + fi + + return + CONVERT( + target_type, + value, + "conv.i4" + ); + else + return + CONVERT( + target_type, + value, + instruction + ); + fi + fi + + let result: Value = + CAST( + target_type, + value + ); + + if !value.type.is_value_type /\ target_type.is_value_type then + result = _conditionally_unbox(result, target_type); + fi + + return result; + si + + _conditionally_unbox( + value: IR.Values.Value, + target_type: Types.Type + ) -> IR.Values.Value + is + // value is the result of a cast instruction ('isinsnt'). we now need + // to check if the result is not null before we attempt to unbox, or + // if the result is null, we return the value type's default value instead + + let block = _block_context.enter_block(target_type); + let brancher = _brancher.get_for(block); + + let temp = TYPE_WRAPPER(_innate_symbol_lookup.get_object_type(), value).get_temp_copier(block, "cast"); + + let want_default_value = IR.LABEL(); + let done = IR.LABEL(); + + // is the `isinst` result null? + brancher.branch(BRANCH.Z, temp(), want_default_value); + + // `isinst` result is not null, so safe to unbox: + block.add( + UNBOX( + TYPE_WRAPPER(target_type, temp()) + ) + ); + + brancher.branch(done); + + brancher.label(want_default_value); + + // 'isinst' result is null, so attempting unbox would throw + // NullReferenceException. return default value instead: + block.add(IR.Values.DEFAULT(target_type)); + + brancher.label(done); + + _block_context.leave_block(); + + return block; + si + si +si \ No newline at end of file diff --git a/src/syntax/process/compile_expressions.ghul b/src/syntax/process/compile_expressions.ghul index ffff19415..4b887101e 100644 --- a/src/syntax/process/compile_expressions.ghul +++ b/src/syntax/process/compile_expressions.ghul @@ -22,6 +22,7 @@ namespace Syntax.Process is _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup; _function_caller: Semantic.FUNCTION_CALLER; _overload_resolver: Semantic.OVERLOAD_RESOLVER; + _type_caster: Semantic.TYPE_CASTER; _symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS; _value_converter: VALUE_CONVERTER; _value_boxer: VALUE_BOXER; @@ -43,6 +44,7 @@ namespace Syntax.Process is symbol_loader: Semantic.SYMBOL_LOADER, innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, function_caller: Semantic.FUNCTION_CALLER, + type_caster: Semantic.TYPE_CASTER, overload_resolver: Semantic.OVERLOAD_RESOLVER, symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, context: IR.CONTEXT, @@ -57,6 +59,7 @@ namespace Syntax.Process is _symbol_loader = symbol_loader; _innate_symbol_lookup = innate_symbol_lookup; _function_caller = function_caller; + _type_caster = type_caster; _overload_resolver = overload_resolver; _symbol_use_locations = symbol_use_locations; _value_converter = value_converter; @@ -1265,66 +1268,24 @@ namespace Syntax.Process is `cast.type_expression.check_is_not_reference(_logger, "cannot cast to a reference type"); - if !`cast.right.value? then - _logger.poison(`cast.right.location, "cast has no value"); - - `cast.value = - DUMMY( - type, - `cast.right.location - ); + if !`cast.right? then + _logger.poison(`cast.location, "cast has no expression"); return; fi - if type.is_value_type /\ `cast.right.value.type.is_value_type then - let instruction = _value_converter.get_instruction(type); - - if !instruction? then - let enum_type = _innate_symbol_lookup.get_enum_type(); + if !`cast.right.value? then + _logger.poison(`cast.right.location, "cast has no value"); - if - !enum_type.is_assignable_from(type) /\ - !enum_type.is_assignable_from(`cast.right.value.type) - then - _logger.error(`cast.location, "cannot convert {`cast.right.value.type} to {`cast.type_expression.type}"); - fi - - `cast.value = - CONVERT( - type, - `cast.right.value, - "conv.i4" - ); - else - `cast.value = - CONVERT( - type, - `cast.right.value, - instruction - ); - fi - return; fi - - if type.is_value_type then - `cast.value = - TYPE_WRAPPER( - type, - UNBOX( - `cast.right.value - ) - ); - return; - fi - - `cast.value = - CAST( - type, - `cast.right.value - ); + // just check the cast is possible at this stage + _type_caster.check_cast_is_valid(`cast.location, `cast.right.value.type, type); + + // we will fill this wrapper with the actual code to cast in the generate IL pass: + `cast.value = + IR.Values.WRAPPER(DUMMY(type, `cast.location)); si visit(`isa: Trees.Expressions.ISA) is diff --git a/src/syntax/process/generate_il.ghul b/src/syntax/process/generate_il.ghul index 2bc21da84..9c2708f61 100644 --- a/src/syntax/process/generate_il.ghul +++ b/src/syntax/process/generate_il.ghul @@ -17,6 +17,7 @@ namespace Syntax.Process is _symbol_loader: Semantic.SYMBOL_LOADER; _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup; _function_caller: Semantic.FUNCTION_CALLER; + _type_caster: Semantic.TYPE_CASTER; _overload_resolver: Semantic.OVERLOAD_RESOLVER; _symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS; @@ -54,6 +55,7 @@ namespace Syntax.Process is symbol_loader: Semantic.SYMBOL_LOADER, innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, function_caller: Semantic.FUNCTION_CALLER, + type_caster: Semantic.TYPE_CASTER, overload_resolver: Semantic.OVERLOAD_RESOLVER, symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, context: CONTEXT, @@ -70,6 +72,7 @@ namespace Syntax.Process is _symbol_loader = symbol_loader; _innate_symbol_lookup = innate_symbol_lookup; _function_caller = function_caller; + _type_caster = type_caster; _overload_resolver = overload_resolver; _symbol_use_locations = symbol_use_locations; _context = context; @@ -503,7 +506,7 @@ namespace Syntax.Process is let symbol = symbol_for(function); current_block.gen(_context); - leave_block(); + leave_block(); if symbol? /\ isa Semantic.Symbols.Function(symbol) then let function_symbol = cast Semantic.Symbols.Function(symbol); @@ -717,6 +720,19 @@ namespace Syntax.Process is visit(statement: Expressions.STATEMENT) is si + visit(`cast: Trees.Expressions.CAST) is + let cast_wrapper = cast Values.WRAPPER(`cast.value); + + cast_wrapper.value = + _type_caster + .cast_value( + `cast.location, + `cast.right.value, + `cast.type_expression.type, + true + ); + si + visit(assign: Statements.ASSIGNMENT) is super.visit(assign);