From 41a32a075c26124f3d5d59df68de9d4f11e59ec7 Mon Sep 17 00:00:00 2001 From: degory Date: Thu, 11 Apr 2024 00:02:39 +0200 Subject: [PATCH] Allow void if expressions Enhancements: - Allow void values in if expressions (closes #1159) - Pass type constraints through if let and nested if (closes #1160) Bugs fixed: - Signature help not available for constructor calls without new (closes #1161) - let in not accepted in return statement (closes #1162) - Lambdas cannot infer void return type (closes #1163) --- .config/dotnet-tools.json | 2 +- Directory.Build.props | 2 +- .../test.ghul | 34 ++++---- src/ir/values/block.ghul | 4 +- src/syntax/parsers/statements/node.ghul | 1 + src/syntax/process/compile_expressions.ghul | 78 ++++++++----------- src/syntax/process/generate_il.ghul | 4 + src/syntax/process/signature_help.ghul | 35 +++++---- src/syntax/trees/expressions/let_in.ghul | 8 +- src/syntax/trees/statements/if.ghul | 4 + 10 files changed, 85 insertions(+), 87 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index fd9ab7aa4..47f587b63 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "ghul.compiler": { - "version": "0.8.36", + "version": "0.8.37", "commands": [ "ghul-compiler" ] diff --git a/Directory.Build.props b/Directory.Build.props index 96cb4127d..f793e35d3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 0.8.37-alpha.131 + 0.8.38-alpha.30 $(NoWarn);NU1507 diff --git a/integration-tests/execution/implied-anonymous-function-argument-types-2/test.ghul b/integration-tests/execution/implied-anonymous-function-argument-types-2/test.ghul index d147ae4a5..c58d63005 100644 --- a/integration-tests/execution/implied-anonymous-function-argument-types-2/test.ghul +++ b/integration-tests/execution/implied-anonymous-function-argument-types-2/test.ghul @@ -1,23 +1,21 @@ -namespace Test is - use Std = IO.Std; +use Std = IO.Std; - class Main is - entry() static is - - - Main().test(); - si - - init() is - si +class Main is + entry() static is - - test() is - Std.write_line("map 123 to string: " + map_int_to_string(123, i => "'" + i + "'")); - return; - si + Main().test(); + si - map_int_to_string(i: int, f: int -> string) -> string static => f(i); + init() is si -si \ No newline at end of file + + + test() is + Std.write_line("map 123 to string: " + map_int_to_string(123, i => "'" + i + "'")); + + return; + si + + map_int_to_string(i: int, f: int -> string) -> string static => f(i); +si diff --git a/src/ir/values/block.ghul b/src/ir/values/block.ghul index f44bb0e4b..222ea04ea 100644 --- a/src/ir/values/block.ghul +++ b/src/ir/values/block.ghul @@ -2,8 +2,6 @@ namespace IR.Values is use TypeTyped = Semantic.Types.Typed; use Semantic.Types.Type; - use Logging; - class BLOCK: Value, TypeTyped is _values: Collections.MutableList[Value]; @@ -14,6 +12,8 @@ namespace IR.Values is is_closed: bool; is_emitted: bool; + is_consumable: bool => true; + init(type: Type) is super.init(); diff --git a/src/syntax/parsers/statements/node.ghul b/src/syntax/parsers/statements/node.ghul index 7b0164ed0..279fefd3b 100644 --- a/src/syntax/parsers/statements/node.ghul +++ b/src/syntax/parsers/statements/node.ghul @@ -63,6 +63,7 @@ namespace Syntax.Parsers.Statements is Lexical.TOKEN.SUPER, Lexical.TOKEN.REC, Lexical.TOKEN.IF, + Lexical.TOKEN.LET, Lexical.TOKEN.OPERATOR ]; diff --git a/src/syntax/process/compile_expressions.ghul b/src/syntax/process/compile_expressions.ghul index e9c5c2a7e..9dabf31d3 100644 --- a/src/syntax/process/compile_expressions.ghul +++ b/src/syntax/process/compile_expressions.ghul @@ -646,8 +646,7 @@ namespace Syntax.Process is if !function.return_type? \/ - function.return_type.is_any \/ - function.return_type =~ _innate_symbol_lookup.get_void_type() + function.return_type.is_any then return false; fi @@ -682,7 +681,7 @@ namespace Syntax.Process is let void_type = _innate_symbol_lookup.get_void_type(); if function.return_type.is_inferred then - if Value.check_is_consumable(_logger, expression.expression.location, expression.expression.value) then + if Value.check_is_consumable_allow_void(_logger, expression.expression.location, expression.expression.value) then function.set_return_type(expression.expression.value.type); elif expression.expression.value? /\ expression.expression.value.type? /\ expression.expression.value.type.is_error then function.set_return_type(Semantic.Types.ERROR()); @@ -711,34 +710,26 @@ namespace Syntax.Process is pre(function: Trees.Expressions.FUNCTION) -> bool is super.pre(function); - if !function.value? then - function.value = IR.Values.WRAPPER(); - fi - - let closure = cast Semantic.Symbols.Closure(scope_for(function)); - - if !closure? then - return true; - fi - - closure.map_type_arguments(); - return true; si - visit(function: Trees.Expressions.FUNCTION) is try _visit(function); catch ex: Exception - debug_always("{function.location} caught {ex}"); - _logger.poison(function.location, "oops: {ex.message}"); + _logger.exception(function.location, ex, "exception walking lambda"); yrt si _visit(function: Trees.Expressions.FUNCTION) is + if !function.value? then + function.value = IR.Values.WRAPPER(); + fi + let closure = cast Semantic.Symbols.Closure(scope_for(function)); + closure.map_type_arguments(); + let implied_type: Type; let implied_argument_types: Collections.List[Type]; @@ -805,9 +796,6 @@ namespace Syntax.Process is else return_type = Semantic.Types.INFERED_RETURN_TYPE(); fi - - // FIXME is this needed? it's not referenced after this point - argument_types.add(return_type); closure.argument_names = argument_names; @@ -1297,7 +1285,7 @@ namespace Syntax.Process is ); si - resolve_constructor(location: LOCATION, right_location: LOCATION, type: Semantic.Types.Type, argument_expressions: Trees.Expressions.LIST) -> Value is + resolve_constructor(location: LOCATION, right_location: LOCATION, type: Semantic.Types.Type, argument_expressions: Trees.Expressions.LIST) -> (Value, Value) is let result: Value; let named_type = cast Semantic.Types.NAMED(type); @@ -1328,23 +1316,15 @@ namespace Syntax.Process is od if !function_group? then - _logger.error(location, "no constructor found init({argument_types|})"); + _logger.error(location, "no constructor found init({argument_types|})"); - return - DUMMY( - type, - location - ); + return (cast Value(DUMMY(type, location)), cast Value(DUMMY(type, location))); fi - let overload_result = _overload_resolver.resolve(location, function_group, argument_types, false, true); + let overload_result = _overload_resolver.resolve(location, function_group, argument_types, true, true); if overload_result == null then - return - DUMMY( - type, - location - ); + return (cast Value(Load.SYMBOL(null, function_group)), cast Value(DUMMY(type, location))); fi let function = overload_result.function; @@ -1364,12 +1344,7 @@ namespace Syntax.Process is _symbol_use_locations.add_symbol_use(right_location, function); _symbol_use_locations.add_symbol_use(right_location, type_symbol.root_unspecialized_symbol); - return - NEW( - type, - function, - arguments - ); + return (cast Value(Load.SYMBOL(null, function_group)), cast Value(NEW(type, function, arguments))); si visit(unary: Trees.Expressions.UNARY) is @@ -2546,7 +2521,7 @@ namespace Syntax.Process is fi if call.function.value.is_type_expression then - call.value = resolve_constructor(call.location, call.right_location, call.function.value.type, call.arguments); + (call.function.value, call.value) = resolve_constructor(call.location, call.right_location, call.function.value.type, call.arguments); return; fi @@ -2685,7 +2660,7 @@ namespace Syntax.Process is fi else if load_symbol? /\ load_symbol.is_type then - call.value = resolve_constructor(call.location, call.right_location, load_symbol.type, call.arguments); + (call.function.value, call.value) = resolve_constructor(call.location, call.right_location, load_symbol.type, call.arguments); else _logger.error(call.function.location, "cannot call value of non-function type {call.function.value.type}"); call.value = DUMMY(Semantic.Types.ERROR(), call.location); @@ -2960,7 +2935,10 @@ namespace Syntax.Process is return; fi - if !`if.branches | .any(b => b? /\ !b.condition?) then + if + !`if.branches | .any(b => b? /\ !b.condition?) /\ + (!`if.constraint? \/ !`if.constraint.is_void) + then // if the if has no else branch, then don't bother reporting any // semantic errors: _logger.error(`if.location, "expected else in if expression"); @@ -2985,12 +2963,18 @@ namespace Syntax.Process is continue; fi - if !branch.value.check_is_consumable(_logger, branch.location) then - continue; + if `if.constraint? then + if !branch.value.check_is_consumable_allow_void(_logger, branch.location) then + continue; + fi + else + if !branch.value.check_is_consumable(_logger, branch.location) then + continue; + fi fi - if `if.constraint? then - if !`if.constraint.is_assignable_from(branch.value.type) then + if `if.constraint? then + if !`if.constraint.is_void /\ !`if.constraint.is_assignable_from(branch.value.type) then _logger.error(branch.location, string.format(`if.constraint_error_message, branch.value.type, type)); fi seen_non_null_values = true; diff --git a/src/syntax/process/generate_il.ghul b/src/syntax/process/generate_il.ghul index d100511e6..2bc21da84 100644 --- a/src/syntax/process/generate_il.ghul +++ b/src/syntax/process/generate_il.ghul @@ -1063,6 +1063,10 @@ namespace Syntax.Process is if `if.want_value /\ b.body.value? then add(b.body.value); + + if `if.constraint? /\ `if.constraint.is_void /\ !b.body.value.type.is_void then + add("pop"); + fi fi brancher.branch(end); diff --git a/src/syntax/process/signature_help.ghul b/src/syntax/process/signature_help.ghul index c53d45425..dea92de1d 100644 --- a/src/syntax/process/signature_help.ghul +++ b/src/syntax/process/signature_help.ghul @@ -41,10 +41,10 @@ namespace Syntax.Process is return; fi - if !(_results?) then - if - !call.location.contains(_target_line, _target_column) \/ - call.function.location.contains(_target_line, _target_column) + if !_results? then + if !call.arguments.location.contains(_target_line, _target_column) /\ + (!call.location.contains(_target_line, _target_column) \/ + call.function.location.contains(_target_line, _target_column)) then return; fi @@ -133,7 +133,15 @@ namespace Syntax.Process is return null; fi - if call.function.value == null then + let load = cast IR.Values.Load.SYMBOL(call.function.value); + + if !load? then + return null; + fi + + let function_group = cast Semantic.Symbols.FUNCTION_GROUP(load.symbol); + + if !function_group? then return null; fi @@ -147,20 +155,13 @@ namespace Syntax.Process is fi od - if isa IR.Values.Load.SYMBOL(call.function.value) then - let load = cast IR.Values.Load.SYMBOL(call.function.value); - - if load.symbol? /\ isa Semantic.Symbols.FUNCTION_GROUP(load.symbol) then - let function_group = cast Semantic.Symbols.FUNCTION_GROUP(load.symbol); - let overload_results = _overload_resolver.find_matches(function_group, argument_types); - - if overload_results.results.count == 0 then - return null; - fi + let overload_results = _overload_resolver.find_matches(function_group, argument_types); - return overload_results; - fi + if overload_results.results.count == 0 then + return null; fi + + return overload_results; si help_for(`new: Trees.Expressions.NEW) -> Semantic.OVERLOAD_MATCHES_RESULT is diff --git a/src/syntax/trees/expressions/let_in.ghul b/src/syntax/trees/expressions/let_in.ghul index 1b66b72a2..cf2b19179 100644 --- a/src/syntax/trees/expressions/let_in.ghul +++ b/src/syntax/trees/expressions/let_in.ghul @@ -1,6 +1,6 @@ namespace Syntax.Trees.Expressions is use Source; - + class LET_IN: Expression is variables: Variables.LIST; expression: Expression; @@ -26,6 +26,12 @@ namespace Syntax.Trees.Expressions is fi si + try_set_constraint(constraint: Semantic.Types.Type, error_message: string) is + if expression? then + expression.try_set_constraint(constraint, error_message); + fi + si + accept(visitor: Visitor) is visitor.visit(self); si diff --git a/src/syntax/trees/statements/if.ghul b/src/syntax/trees/statements/if.ghul index 14856613b..900a7666b 100644 --- a/src/syntax/trees/statements/if.ghul +++ b/src/syntax/trees/statements/if.ghul @@ -16,6 +16,10 @@ namespace Syntax.Trees.Statements is try_set_constraint(constraint: Semantic.Types.Type, error_message: string) is self.constraint = constraint; self.constraint_error_message = error_message; + + for b in branches do + b.body.try_set_constraint(constraint, error_message); + od si accept(visitor: Visitor) is