Skip to content

Commit

Permalink
Allow void if expressions (#1164)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
degory authored Apr 10, 2024
1 parent 15a1ca6 commit ba97fc5
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 87 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
]
},
"ghul.compiler": {
"version": "0.8.36",
"version": "0.8.37",
"commands": [
"ghul-compiler"
]
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.8.37-alpha.131</Version>
<Version>0.8.38-alpha.30</Version>
<NoWarn>$(NoWarn);NU1507</NoWarn>
</PropertyGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -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


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
4 changes: 2 additions & 2 deletions src/ir/values/block.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -14,6 +12,8 @@ namespace IR.Values is
is_closed: bool;
is_emitted: bool;

is_consumable: bool => true;

init(type: Type) is
super.init();

Expand Down
1 change: 1 addition & 0 deletions src/syntax/parsers/statements/node.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ namespace Syntax.Parsers.Statements is
Lexical.TOKEN.SUPER,
Lexical.TOKEN.REC,
Lexical.TOKEN.IF,
Lexical.TOKEN.LET,
Lexical.TOKEN.OPERATOR
];

Expand Down
78 changes: 31 additions & 47 deletions src/syntax/process/compile_expressions.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand All @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/syntax/process/generate_il.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
35 changes: 18 additions & 17 deletions src/syntax/process/signature_help.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/syntax/trees/expressions/let_in.ghul
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Syntax.Trees.Expressions is
use Source;

class LET_IN: Expression is
variables: Variables.LIST;
expression: Expression;
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/syntax/trees/statements/if.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ba97fc5

Please sign in to comment.