Skip to content

Commit

Permalink
Type inference improvements (#1176)
Browse files Browse the repository at this point in the history
Bugs fixed:
- Types with unsafe generic parameter constraints could be inferred as element types
- Tuple elements without names could cause internal errors

Enhancements:
- Tweak least upper bound quality heuristic
  • Loading branch information
degory authored Apr 17, 2024
1 parent fd0e159 commit 5b69882
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 42 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.43",
"version": "0.8.44",
"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.44-alpha.80</Version>
<Version>0.8.45-alpha.5</Version>
<NoWarn>$(NoWarn);NU1507</NoWarn>
</PropertyGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
test.ghul: 14,18..14,24: error: no static overload found for function(Ghul.int, Ghul.single), tried Test.GenericFunctionTypeInference.function[T](a: T, b: T) -> T
test.ghul: 24,18..24,40: error: no static overload found for function(Ghul.int[], Ghul.object[]), tried Test.GenericFunctionTypeInference.function[T](a: T, b: T) -> T
test.ghul: 27,18..27,58: error: no static overload found for function(Ghul.int[], Collections.LIST[Ghul.int]), tried Test.GenericFunctionTypeInference.function[T](a: T, b: T) -> T
test.ghul: 28,18..28,40: error: no static overload found for function(Ghul.int[], Ghul.Pipes.Pipe[Ghul.int]), tried Test.GenericFunctionTypeInference.function[T](a: T, b: T) -> T
test.ghul: 13,14..13,20: error: no static overload found for function(Ghul.int, Ghul.single), tried function[T](a: T, b: T) -> T
test.ghul: 24,14..24,36: error: no static overload found for function(Ghul.int[], System.ISpanFormattable[]), tried function[T](a: T, b: T) -> T
test.ghul: 27,14..27,54: error: no static overload found for function(Ghul.int[], Collections.LIST[Ghul.int]), tried function[T](a: T, b: T) -> T
test.ghul: 28,14..28,36: error: no static overload found for function(Ghul.int[], Ghul.Pipes.Pipe[Ghul.int]), tried function[T](a: T, b: T) -> T
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
namespace Test.GenericFunctionTypeInference is
use Collections;
use Collections;

entry() is
function(1, 2);
function(1.0, 2.0);
entry() is
function(1, 2);

function(1.0, 2.0);

function("a", "b");
function("a", "b");

function(true, false);
function(true, false);

// expect an error
function(1, 2.0);
// expect an error
function(1, 2.0);

function([1234], [5678]);
function([1234], [5678]);


function([1, 2.0], [3, 4.0]);
function([1, 2.0], [3, 4.0]);

function([1, 2.0, "a"], [3, 4.0, "b"]);
function([1, 2.0, "a"], [3, 4.0, "b"]);

// expect an error
function([1, 2, 3], [4, 5, 6.0]);
// expect error
// infers a not very useful interface - LUB needs tuning
function([1, 2, 3], [4, 5, 6.0]);

// expect errors. Note these don't work in C# either:
function([1234, 5678], LIST[int]([91011, 121314]));
function([1, 2, 3], [4, 5, 6] |);
si
// expect errors. Note these don't work in C# either:
function([1234, 5678], LIST[int]([91011, 121314]));
function([1, 2, 3], [4, 5, 6] |);
si

function[T](a: T, b: T) -> T is
return a;
si
function[T](a: T, b: T) -> T is
return a;
si
2 changes: 1 addition & 1 deletion src/semantic/dotnet/symbol_factory.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ namespace Semantic.DotNet is
fi

return t.get_generic_parameter_constraints().count == 0;
si
si

add_members(result: Symbols.Scoped, type: TYPE) is
let properties = MAP[System.Reflection.MethodInfo,PROPERTY_DETAILS]();
Expand Down
6 changes: 1 addition & 5 deletions src/semantic/types/tuple.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ namespace Semantic.Types is
assert args? else "tuple args are null";
assert args | .all(a => a?) else "tuple at least one null argument";

assert !names? \/ names | .all(n => n?) else "tuple at least one null name";

return
if !args? then
"()";
Expand All @@ -27,7 +25,7 @@ namespace Semantic.Types is
si

_get_element_name(names: Collections.List[string], index: int) -> string static =>
if names? /\ index < names.count then
if names? /\ index < names.count /\ names[index]? then
"{names[index]}: "
else
""
Expand All @@ -41,8 +39,6 @@ namespace Semantic.Types is
) is
super.init(Symbols.TUPLE(location, symbol, arguments, names));

assert !names? \/ names | .all(n => n?) else "tuple at least one null name";

self.names = names;
si

Expand Down
1 change: 1 addition & 0 deletions src/semantic/types/type.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ namespace Semantic.Types is
is_function_with_any_implicit_argument_types: bool => false;
is_ref: bool => false; // specifically 'ref', not just a reference type
is_value_tuple: bool => false;
is_unsafe_constraints: bool => symbol.is_unsafe_constraints;

init() is
si
Expand Down
2 changes: 1 addition & 1 deletion src/syntax/process/compile_expressions.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ namespace Syntax.Process is
types.add(Semantic.Types.ERROR());

elif Value.check_is_consumable(_logger, v.location, v.value) then
names.add(null);
names.add("`{index}");

if element_type_constraint? then
let type = element_type_constraint;
Expand Down
38 changes: 31 additions & 7 deletions src/syntax/process/least_upper_bound_map.ghul
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Syntax.Process is
si

class LEAST_UPPER_BOUND_MAP is
_any_unsafe_constraints: bool;
_mode: LUB_MODE;

types: LIST[Type];
Expand All @@ -28,6 +29,10 @@ namespace Syntax.Process is
add(type: Type) is
types.add(type);

if type.is_unsafe_constraints then
_any_unsafe_constraints = true;
fi

_update_element_names_from(type);
si

Expand Down Expand Up @@ -71,20 +76,33 @@ namespace Syntax.Process is
// case it will return object, which may still be better
// than a fairly random choice of interface type
let best_concrete_type = _try_get_best_concrete();
if best_concrete_type? then
return best_concrete_type;
fi

// this is LUB applied to all elements and all traits
// they implement. It has a tendency to select unhelpful
// traits, and needs tuning somehow to prioritize
// traits that are relevant based on context
let best_trait_type = _try_get_best_trait();
if best_trait_type? then
return best_trait_type;
fi

return null;
// this still might not be enough - we might need to record the
// average depth difference (='specificity') per type, but that
// could be slow
if best_concrete_type? /\ best_trait_type? then
let total_concrete_depth_difference =
types | .reduce(0, (d, t) => d + (t.depth - best_concrete_type.depth));

let total_trait_depth_difference =
types | .reduce(0, (d, t) => d + (t.depth - best_trait_type.depth));

if total_trait_depth_difference < total_concrete_depth_difference then
return best_trait_type
else
return best_concrete_type
fi
elif best_concrete_type? then
return best_concrete_type
else
return best_trait_type
fi
si

_try_get_best_assignable() -> Type is
Expand Down Expand Up @@ -149,6 +167,12 @@ namespace Syntax.Process is

for list in result.values do
for type in list do
if !_any_unsafe_constraints /\ type.is_unsafe_constraints then
// only allow unsafe constraints if some of the element
// types have unsafe constraints:
continue;
fi

if !best? then
best = type;
is_ambiguous = false;
Expand Down

0 comments on commit 5b69882

Please sign in to comment.