mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Merge pull request #8335 from roc-lang/jared/push-rlpsnsmkslnm
Fix rigid type static dispatch validation
This commit is contained in:
commit
e89ca2d318
6 changed files with 214 additions and 189 deletions
|
|
@ -99,6 +99,8 @@ bool_var: Var,
|
|||
deferred_static_dispatch_constraints: DeferredConstraintCheck.SafeList,
|
||||
/// Used when looking up static dispatch functions
|
||||
static_dispatch_method_name_buf: std.ArrayList(u8),
|
||||
/// Map representation of Ident -> Var, used in checking static dispatch constraints
|
||||
ident_to_var_map: std.AutoHashMap(Ident.Idx, Var),
|
||||
|
||||
/// A map of rigid variables that we build up during a branch of type checking
|
||||
const FreeVar = struct { ident: base.Ident.Idx, var_: Var };
|
||||
|
|
@ -159,6 +161,7 @@ pub fn init(
|
|||
.bool_var = undefined, // Will be initialized in copyBuiltinTypes()
|
||||
.deferred_static_dispatch_constraints = try DeferredConstraintCheck.SafeList.initCapacity(gpa, 128),
|
||||
.static_dispatch_method_name_buf = try std.ArrayList(u8).initCapacity(gpa, 32),
|
||||
.ident_to_var_map = std.AutoHashMap(Ident.Idx, Var).init(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +186,7 @@ pub fn deinit(self: *Self) void {
|
|||
self.constraint_origins.deinit();
|
||||
self.deferred_static_dispatch_constraints.deinit(self.gpa);
|
||||
self.static_dispatch_method_name_buf.deinit(self.gpa);
|
||||
self.ident_to_var_map.deinit();
|
||||
}
|
||||
|
||||
/// Assert that type vars and regions in sync
|
||||
|
|
@ -3749,22 +3753,70 @@ fn checkDeferredStaticDispatchConstraints(self: *Self) std.mem.Allocator.Error!v
|
|||
const dispatcher_content = dispatcher_resolved.desc.content;
|
||||
|
||||
if (dispatcher_content == .err) {
|
||||
|
||||
// If the root type is an error, then skip constraint checking
|
||||
// Iterate over the constraints
|
||||
const constraints = self.types.sliceStaticDispatchConstraints(deferred_constraint.constraints);
|
||||
for (constraints) |constraint| {
|
||||
try self.markConstraintFunctionAsError(constraint);
|
||||
}
|
||||
try self.updateVar(deferred_constraint.var_, .err, Rank.generalized);
|
||||
} else if (dispatcher_content == .rigid) {
|
||||
// Get the rigid variable and the constraints it has defined
|
||||
const rigid = dispatcher_content.rigid;
|
||||
const rigid_constraints = self.types.sliceStaticDispatchConstraints(rigid.constraints);
|
||||
|
||||
// Get the deferred constraints to validate against
|
||||
const deferred_constraints = self.types.sliceStaticDispatchConstraints(deferred_constraint.constraints);
|
||||
|
||||
// First, special case if this rigid has no constraints
|
||||
if (deferred_constraints.len > 0 and rigid_constraints.len == 0) {
|
||||
const constraint = deferred_constraints[0];
|
||||
try self.reportConstraintError(
|
||||
deferred_constraint.var_,
|
||||
constraint,
|
||||
.{ .missing_method = .rigid },
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build a map of constraints the rigid has
|
||||
self.ident_to_var_map.clearRetainingCapacity();
|
||||
try self.ident_to_var_map.ensureUnusedCapacity(@intCast(rigid_constraints.len));
|
||||
for (rigid_constraints) |rigid_constraint| {
|
||||
self.ident_to_var_map.putAssumeCapacity(rigid_constraint.fn_name, rigid_constraint.fn_var);
|
||||
}
|
||||
|
||||
// Iterate over the constraints
|
||||
for (deferred_constraints) |constraint| {
|
||||
// Extract the function and return type from the constraint
|
||||
const resolved_constraint = self.types.resolveVar(constraint.fn_var);
|
||||
const mb_resolved_func = resolved_constraint.desc.content.unwrapFunc();
|
||||
std.debug.assert(mb_resolved_func != null);
|
||||
const resolved_func = mb_resolved_func.?;
|
||||
|
||||
// Set it to be an error
|
||||
try self.updateVar(resolved_func.ret, .err, Rank.generalized);
|
||||
// Then, lookup the inferred constraint in the actual list of rigid constraints
|
||||
if (self.ident_to_var_map.get(constraint.fn_name)) |rigid_var| {
|
||||
// Unify the actual function var against the inferred var
|
||||
//
|
||||
// TODO: For better error messages, we should check if these
|
||||
// types are functions, unify each arg, etc. This should look
|
||||
// similar to e_call
|
||||
const result = try self.unify(rigid_var, constraint.fn_var, Rank.generalized);
|
||||
if (result.isProblem()) {
|
||||
try self.updateVar(deferred_constraint.var_, .err, Rank.generalized);
|
||||
try self.updateVar(resolved_func.ret, .err, Rank.generalized);
|
||||
}
|
||||
} else {
|
||||
try self.reportConstraintError(
|
||||
deferred_constraint.var_,
|
||||
constraint,
|
||||
.{ .missing_method = .nominal },
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (dispatcher_content == .rigid or dispatcher_content == .flex) {
|
||||
// If the root type is an flex or rigid, then we there's nothing to check
|
||||
// since the type is not concrete
|
||||
} else if (dispatcher_content == .flex) {
|
||||
// If the root type is aa flex, then we there's nothing to check
|
||||
continue;
|
||||
} else if (dispatcher_content == .structure and dispatcher_content.structure == .nominal_type) {
|
||||
// TODO: Internal types like Str, Result, List, etc are not
|
||||
|
|
@ -3822,36 +3874,24 @@ fn checkDeferredStaticDispatchConstraints(self: *Self) std.mem.Allocator.Error!v
|
|||
|
||||
// Get the ident of this method in the original env
|
||||
const ident_in_original_env = original_env.getIdentStoreConst().findByString(qualified_name_bytes) orelse {
|
||||
const snapshot = try self.snapshots.deepCopyVar(self.types, deferred_constraint.var_);
|
||||
_ = try self.problems.appendProblem(self.cir.gpa, .{ .static_dispach = .{
|
||||
.dispatcher_does_not_impl_method = .{
|
||||
.dispatcher_var = deferred_constraint.var_,
|
||||
.dispatcher_snapshot = snapshot,
|
||||
.fn_var = constraint.fn_var,
|
||||
.method_name = constraint.fn_name,
|
||||
},
|
||||
} });
|
||||
try self.updateVar(resolved_func.ret, .err, Rank.generalized);
|
||||
try self.reportConstraintError(
|
||||
deferred_constraint.var_,
|
||||
constraint,
|
||||
.{ .missing_method = .nominal },
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the def index in the original env
|
||||
const node_idx_in_original_env = original_env.getExposedNodeIndexById(ident_in_original_env) orelse {
|
||||
// This can happen if somehow, the original module has
|
||||
// an ident that matches the method/type, but it doesn't
|
||||
// actually have/expose the method. This should be
|
||||
// impossible, but we handle it gracefully
|
||||
|
||||
const snapshot = try self.snapshots.deepCopyVar(self.types, deferred_constraint.var_);
|
||||
_ = try self.problems.appendProblem(self.cir.gpa, .{ .static_dispach = .{
|
||||
.dispatcher_does_not_impl_method = .{
|
||||
.dispatcher_var = deferred_constraint.var_,
|
||||
.dispatcher_snapshot = snapshot,
|
||||
.fn_var = constraint.fn_var,
|
||||
.method_name = constraint.fn_name,
|
||||
},
|
||||
} });
|
||||
try self.updateVar(resolved_func.ret, .err, Rank.generalized);
|
||||
// This can happen if the original module has an ident that
|
||||
// matches the method/type, but it doesn't actually have
|
||||
// that method.
|
||||
try self.reportConstraintError(
|
||||
deferred_constraint.var_,
|
||||
constraint,
|
||||
.{ .missing_method = .nominal },
|
||||
);
|
||||
continue;
|
||||
};
|
||||
const def_idx: Var = @enumFromInt(@as(u32, @intCast(node_idx_in_original_env)));
|
||||
|
|
@ -3869,6 +3909,7 @@ fn checkDeferredStaticDispatchConstraints(self: *Self) std.mem.Allocator.Error!v
|
|||
// similar to e_call
|
||||
const result = try self.unify(real_method_var, constraint.fn_var, Rank.generalized);
|
||||
if (result.isProblem()) {
|
||||
try self.updateVar(deferred_constraint.var_, .err, Rank.generalized);
|
||||
try self.updateVar(resolved_func.ret, .err, Rank.generalized);
|
||||
}
|
||||
}
|
||||
|
|
@ -3877,27 +3918,11 @@ fn checkDeferredStaticDispatchConstraints(self: *Self) std.mem.Allocator.Error!v
|
|||
|
||||
const constraints = self.types.sliceStaticDispatchConstraints(deferred_constraint.constraints);
|
||||
if (constraints.len > 0) {
|
||||
const constraint = constraints[0];
|
||||
|
||||
// Snapshot the constraint and add a problem
|
||||
const snapshot = try self.snapshots.deepCopyVar(self.types, deferred_constraint.var_);
|
||||
_ = try self.problems.appendProblem(self.cir.gpa, .{ .static_dispach = .{
|
||||
.dispatcher_not_nominal = .{
|
||||
.dispatcher_var = deferred_constraint.var_,
|
||||
.dispatcher_snapshot = snapshot,
|
||||
.fn_var = constraint.fn_var,
|
||||
.method_name = constraint.fn_name,
|
||||
},
|
||||
} });
|
||||
|
||||
// Extract the function and return type from the constraint
|
||||
const resolved_constraint = self.types.resolveVar(constraint.fn_var);
|
||||
const mb_resolved_func = resolved_constraint.desc.content.unwrapFunc();
|
||||
std.debug.assert(mb_resolved_func != null);
|
||||
const resolved_func = mb_resolved_func.?;
|
||||
|
||||
// Set it to be an error
|
||||
try self.updateVar(resolved_func.ret, .err, Rank.generalized);
|
||||
try self.reportConstraintError(
|
||||
deferred_constraint.var_,
|
||||
constraints[0],
|
||||
.not_nominal,
|
||||
);
|
||||
} else {
|
||||
// It should be impossible to have a deferred constraint check
|
||||
// that has no constraints.
|
||||
|
|
@ -3906,3 +3931,49 @@ fn checkDeferredStaticDispatchConstraints(self: *Self) std.mem.Allocator.Error!v
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a constraint function's return type as error
|
||||
fn markConstraintFunctionAsError(self: *Self, constraint: StaticDispatchConstraint) !void {
|
||||
const resolved_constraint = self.types.resolveVar(constraint.fn_var);
|
||||
const mb_resolved_func = resolved_constraint.desc.content.unwrapFunc();
|
||||
std.debug.assert(mb_resolved_func != null);
|
||||
const resolved_func = mb_resolved_func.?;
|
||||
try self.updateVar(resolved_func.ret, .err, Rank.generalized);
|
||||
}
|
||||
|
||||
/// Report a constraint validation error
|
||||
fn reportConstraintError(
|
||||
self: *Self,
|
||||
dispatcher_var: Var,
|
||||
constraint: StaticDispatchConstraint,
|
||||
kind: union(enum) {
|
||||
missing_method: problem.DispatcherDoesNotImplMethod.DispatcherType,
|
||||
not_nominal,
|
||||
},
|
||||
) !void {
|
||||
const snapshot = try self.snapshots.deepCopyVar(self.types, dispatcher_var);
|
||||
|
||||
const constraint_problem = switch (kind) {
|
||||
.missing_method => |dispatcher_type| problem.Problem{ .static_dispach = .{
|
||||
.dispatcher_does_not_impl_method = .{
|
||||
.dispatcher_var = dispatcher_var,
|
||||
.dispatcher_snapshot = snapshot,
|
||||
.dispatcher_type = dispatcher_type,
|
||||
.fn_var = constraint.fn_var,
|
||||
.method_name = constraint.fn_name,
|
||||
},
|
||||
} },
|
||||
.not_nominal => problem.Problem{ .static_dispach = .{
|
||||
.dispatcher_not_nominal = .{
|
||||
.dispatcher_var = dispatcher_var,
|
||||
.dispatcher_snapshot = snapshot,
|
||||
.fn_var = constraint.fn_var,
|
||||
.method_name = constraint.fn_name,
|
||||
},
|
||||
} },
|
||||
};
|
||||
|
||||
_ = try self.problems.appendProblem(self.cir.gpa, constraint_problem);
|
||||
try self.markConstraintFunctionAsError(constraint);
|
||||
try self.updateVar(dispatcher_var, .err, Rank.generalized);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,8 +222,12 @@ pub const DispatcherNotNominal = struct {
|
|||
pub const DispatcherDoesNotImplMethod = struct {
|
||||
dispatcher_var: Var,
|
||||
dispatcher_snapshot: SnapshotContentIdx,
|
||||
dispatcher_type: DispatcherType,
|
||||
fn_var: Var,
|
||||
method_name: Ident.Idx,
|
||||
|
||||
/// Type of the dispatcher
|
||||
pub const DispatcherType = enum { nominal, rigid };
|
||||
};
|
||||
|
||||
// bug //
|
||||
|
|
@ -1695,9 +1699,18 @@ pub const ReportBuilder = struct {
|
|||
try report.document.addLineBreak();
|
||||
try report.document.addLineBreak();
|
||||
try report.document.addAnnotated("Hint:", .emphasized);
|
||||
try report.document.addReflowingText(" Did you forget to define ");
|
||||
try report.document.addAnnotated(method_name_str, .emphasized);
|
||||
try report.document.addReflowingText(" in the type's method block?");
|
||||
switch (data.dispatcher_type) {
|
||||
.nominal => {
|
||||
try report.document.addReflowingText(" Did you forget to define ");
|
||||
try report.document.addAnnotated(method_name_str, .emphasized);
|
||||
try report.document.addReflowingText(" in the type's method block?");
|
||||
},
|
||||
.rigid => {
|
||||
try report.document.addReflowingText(" Did you forget to specify ");
|
||||
try report.document.addAnnotated(method_name_str, .emphasized);
|
||||
try report.document.addReflowingText(" in the type annotation?");
|
||||
},
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1218,6 +1218,23 @@ test "check type - static dispatch - concrete - indirection 2" {
|
|||
);
|
||||
}
|
||||
|
||||
test "check type - static dispatch - fail if not in type signature" {
|
||||
const source =
|
||||
\\module []
|
||||
\\
|
||||
\\main : a -> a
|
||||
\\main = |a| {
|
||||
\\ _val = a.method()
|
||||
\\ a
|
||||
\\}
|
||||
;
|
||||
try checkTypesModule(
|
||||
source,
|
||||
.fail,
|
||||
"MISSING METHOD",
|
||||
);
|
||||
}
|
||||
|
||||
// helpers - module //
|
||||
|
||||
const ModuleExpectation = union(enum) {
|
||||
|
|
|
|||
|
|
@ -4031,7 +4031,7 @@ test "unify - flex with subset of constraints (a subset b)" {
|
|||
try std.testing.expectEqual(2, result_constraints.len());
|
||||
}
|
||||
|
||||
test "unify - flex with constraints vs rigid with subset constraints" {
|
||||
test "unify - flex with constraints vs rigid with constraints" {
|
||||
const gpa = std.testing.allocator;
|
||||
var env = try TestEnv.init(gpa);
|
||||
defer env.deinit();
|
||||
|
|
@ -4041,13 +4041,15 @@ test "unify - flex with constraints vs rigid with subset constraints" {
|
|||
|
||||
// flex has 2 constraints
|
||||
const foo_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{str}, str));
|
||||
const foo_ident = try env.module_env.getIdentStore().insert(env.module_env.gpa, Ident.for_text("foo"));
|
||||
const foo_constraint = types_mod.StaticDispatchConstraint{
|
||||
.fn_name = try env.module_env.getIdentStore().insert(env.module_env.gpa, Ident.for_text("foo")),
|
||||
.fn_name = foo_ident,
|
||||
.fn_var = foo_fn,
|
||||
};
|
||||
const bar_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{int}, int));
|
||||
const bar_ident = try env.module_env.getIdentStore().insert(env.module_env.gpa, Ident.for_text("bar"));
|
||||
const bar_constraint = types_mod.StaticDispatchConstraint{
|
||||
.fn_name = try env.module_env.getIdentStore().insert(env.module_env.gpa, Ident.for_text("bar")),
|
||||
.fn_name = bar_ident,
|
||||
.fn_var = bar_fn,
|
||||
};
|
||||
const flex_constraints = try env.module_env.types.appendStaticDispatchConstraints(&[_]types_mod.StaticDispatchConstraint{ foo_constraint, bar_constraint });
|
||||
|
|
@ -4066,10 +4068,20 @@ test "unify - flex with constraints vs rigid with subset constraints" {
|
|||
} });
|
||||
|
||||
const result = try env.unify(flex_var, rigid_var);
|
||||
try std.testing.expectEqual(false, result.isOk());
|
||||
try std.testing.expectEqual(true, result.isOk());
|
||||
|
||||
try std.testing.expectEqual(1, env.scratch.deferred_constraints.len());
|
||||
const deferred = env.scratch.deferred_constraints.get(@enumFromInt(0));
|
||||
try std.testing.expectEqual(rigid_var, deferred.var_);
|
||||
|
||||
try std.testing.expectEqual(2, deferred.constraints.len());
|
||||
const constraint1 = env.module_env.types.static_dispatch_constraints.get(@enumFromInt(0));
|
||||
try std.testing.expectEqual(foo_ident, constraint1.fn_name);
|
||||
const constraint2 = env.module_env.types.static_dispatch_constraints.get(@enumFromInt(1));
|
||||
try std.testing.expectEqual(bar_ident, constraint2.fn_name);
|
||||
}
|
||||
|
||||
test "unify - flex with constraints vs rigid with superset constraints" {
|
||||
test "unify - flex with constraints vs rigid constraints 2" {
|
||||
const gpa = std.testing.allocator;
|
||||
var env = try TestEnv.init(gpa);
|
||||
defer env.deinit();
|
||||
|
|
@ -4079,8 +4091,9 @@ test "unify - flex with constraints vs rigid with superset constraints" {
|
|||
|
||||
// flex has 1 constraint
|
||||
const foo_fn = try env.module_env.types.freshFromContent(try env.mkFuncPure(&[_]Var{str}, str));
|
||||
const foo_ident = try env.module_env.getIdentStore().insert(env.module_env.gpa, Ident.for_text("foo"));
|
||||
const foo_constraint = types_mod.StaticDispatchConstraint{
|
||||
.fn_name = try env.module_env.getIdentStore().insert(env.module_env.gpa, Ident.for_text("foo")),
|
||||
.fn_name = foo_ident,
|
||||
.fn_var = foo_fn,
|
||||
};
|
||||
const flex_constraints = try env.module_env.types.appendStaticDispatchConstraints(&[_]types_mod.StaticDispatchConstraint{foo_constraint});
|
||||
|
|
@ -4105,6 +4118,13 @@ test "unify - flex with constraints vs rigid with superset constraints" {
|
|||
|
||||
const result = try env.unify(flex_var, rigid_var);
|
||||
try std.testing.expectEqual(.ok, result);
|
||||
|
||||
try std.testing.expectEqual(1, env.scratch.deferred_constraints.len());
|
||||
const deferred = env.scratch.deferred_constraints.get(@enumFromInt(0));
|
||||
try std.testing.expectEqual(rigid_var, deferred.var_);
|
||||
try std.testing.expectEqual(1, deferred.constraints.len());
|
||||
const constraint = env.module_env.types.static_dispatch_constraints.get(@enumFromInt(0));
|
||||
try std.testing.expectEqual(foo_ident, constraint.fn_name);
|
||||
}
|
||||
|
||||
test "unify - empty constraints unify with any" {
|
||||
|
|
|
|||
|
|
@ -533,18 +533,22 @@ const Unifier = struct {
|
|||
}
|
||||
};
|
||||
|
||||
const merged_constraints = try self.unifyStaticDispatchConstraints(a_flex.constraints, b_flex.constraints, .union_all);
|
||||
const merged_constraints = try self.unifyStaticDispatchConstraints(a_flex.constraints, b_flex.constraints);
|
||||
self.merge(vars, Content{ .flex = .{
|
||||
.name = mb_ident,
|
||||
.constraints = merged_constraints,
|
||||
} });
|
||||
},
|
||||
.rigid => |b_rigid| {
|
||||
const merged_constraints = try self.unifyStaticDispatchConstraints(a_flex.constraints, b_rigid.constraints, .a_subset_b);
|
||||
self.merge(vars, Content{ .rigid = .{
|
||||
.name = b_rigid.name,
|
||||
.constraints = merged_constraints,
|
||||
} });
|
||||
if (a_flex.constraints.len() > 0) {
|
||||
// Record that we need to check constraints later
|
||||
_ = self.scratch.deferred_constraints.append(self.scratch.gpa, DeferredConstraintCheck{
|
||||
.var_ = vars.b.var_, // Since the vars are merge, we arbitrary choose b
|
||||
.constraints = a_flex.constraints,
|
||||
}) catch return Error.AllocatorError;
|
||||
}
|
||||
|
||||
self.merge(vars, .{ .rigid = b_rigid });
|
||||
},
|
||||
.alias => |b_alias| {
|
||||
if (a_flex.constraints.len() == 0) {
|
||||
|
|
@ -579,11 +583,15 @@ const Unifier = struct {
|
|||
|
||||
switch (b_content) {
|
||||
.flex => |b_flex| {
|
||||
const merged_constraints = try self.unifyStaticDispatchConstraints(a_rigid.constraints, b_flex.constraints, .b_subset_a);
|
||||
self.merge(vars, Content{ .rigid = .{
|
||||
.name = a_rigid.name,
|
||||
.constraints = merged_constraints,
|
||||
} });
|
||||
if (b_flex.constraints.len() > 0) {
|
||||
// Record that we need to check constraints later
|
||||
_ = self.scratch.deferred_constraints.append(self.scratch.gpa, DeferredConstraintCheck{
|
||||
.var_ = vars.b.var_, // Since the vars are merge, we arbitrary choose b
|
||||
.constraints = b_flex.constraints,
|
||||
}) catch return Error.AllocatorError;
|
||||
}
|
||||
|
||||
self.merge(vars, .{ .rigid = a_rigid });
|
||||
},
|
||||
.rigid => return error.TypeMismatch,
|
||||
.alias => return error.TypeMismatch,
|
||||
|
|
@ -2897,54 +2905,26 @@ const Unifier = struct {
|
|||
|
||||
// constraints //
|
||||
|
||||
const ConstraintMergeStrategy = enum {
|
||||
/// Take union of all constraints (flex + flex)
|
||||
union_all,
|
||||
/// Require a ⊆ b, return b's constraints (flex + rigid)
|
||||
a_subset_b,
|
||||
/// Require b ⊆ a, return a's constraints (rigid + flex)
|
||||
b_subset_a,
|
||||
};
|
||||
|
||||
fn unifyStaticDispatchConstraints(
|
||||
self: *Self,
|
||||
a_constraints: StaticDispatchConstraint.SafeList.Range,
|
||||
b_constraints: StaticDispatchConstraint.SafeList.Range,
|
||||
strategy: ConstraintMergeStrategy,
|
||||
) Error!StaticDispatchConstraint.SafeList.Range {
|
||||
const a_len = a_constraints.len();
|
||||
const b_len = b_constraints.len();
|
||||
|
||||
// Early exits for empty ranges
|
||||
if (a_len == 0 and b_len == 0) {
|
||||
return StaticDispatchConstraint.SafeList.Range.empty();
|
||||
}
|
||||
if (a_len == 0) return if (strategy == .b_subset_a) a_constraints else b_constraints;
|
||||
if (b_len == 0) return if (strategy == .a_subset_b) b_constraints else a_constraints;
|
||||
|
||||
// Subset validation
|
||||
switch (strategy) {
|
||||
.a_subset_b => if (a_len > b_len) return error.TypeMismatch,
|
||||
.b_subset_a => if (b_len > a_len) return error.TypeMismatch,
|
||||
.union_all => {},
|
||||
return .empty();
|
||||
} else if (a_len == 0 and b_len > 0) {
|
||||
return b_constraints;
|
||||
} else if (a_len > 0 and b_len == 0) {
|
||||
return a_constraints;
|
||||
}
|
||||
|
||||
// Partition constraints
|
||||
const partitioned = self.partitionStaticDispatchConstraints(a_constraints, b_constraints) catch return Error.AllocatorError;
|
||||
|
||||
// Check subset requirements
|
||||
switch (strategy) {
|
||||
.a_subset_b => if (partitioned.only_in_a.len() > 0) {
|
||||
// TODO: Throw custom error message
|
||||
return error.TypeMismatch;
|
||||
},
|
||||
.b_subset_a => if (partitioned.only_in_b.len() > 0) {
|
||||
// TODO: Throw custom error message
|
||||
return error.TypeMismatch;
|
||||
},
|
||||
.union_all => {},
|
||||
}
|
||||
|
||||
// Unify shared constraints
|
||||
if (partitioned.in_both.len() > 0) {
|
||||
for (self.scratch.in_both_static_dispatch_constraints.sliceRange(partitioned.in_both)) |two_constraints| {
|
||||
|
|
@ -2953,45 +2933,24 @@ const Unifier = struct {
|
|||
}
|
||||
}
|
||||
|
||||
// Build result based on strategy
|
||||
const top: u32 = @intCast(self.types_store.static_dispatch_constraints.len());
|
||||
|
||||
const capacity = partitioned.in_both.len() + switch (strategy) {
|
||||
.union_all => partitioned.only_in_a.len() + partitioned.only_in_b.len(),
|
||||
.a_subset_b => partitioned.only_in_b.len(),
|
||||
.b_subset_a => partitioned.only_in_a.len(),
|
||||
};
|
||||
|
||||
// Ensure we have enough memory for the new contiguous list
|
||||
const capacity = partitioned.in_both.len() + partitioned.only_in_a.len() + partitioned.only_in_b.len();
|
||||
self.types_store.static_dispatch_constraints.items.ensureUnusedCapacity(
|
||||
self.types_store.gpa,
|
||||
capacity,
|
||||
) catch return Error.AllocatorError;
|
||||
|
||||
// Always append shared constraints (using b's version)
|
||||
for (self.scratch.in_both_static_dispatch_constraints.sliceRange(partitioned.in_both)) |two_constraints| {
|
||||
// Here, we append the constraint's b, but since a & b, it doesn't actually matter
|
||||
self.types_store.static_dispatch_constraints.items.appendAssumeCapacity(two_constraints.b);
|
||||
}
|
||||
|
||||
// Append unique constraints based on strategy
|
||||
switch (strategy) {
|
||||
.union_all => {
|
||||
for (self.scratch.only_in_a_static_dispatch_constraints.sliceRange(partitioned.only_in_a)) |only_a| {
|
||||
self.types_store.static_dispatch_constraints.items.appendAssumeCapacity(only_a);
|
||||
}
|
||||
for (self.scratch.only_in_b_static_dispatch_constraints.sliceRange(partitioned.only_in_b)) |only_b| {
|
||||
self.types_store.static_dispatch_constraints.items.appendAssumeCapacity(only_b);
|
||||
}
|
||||
},
|
||||
.a_subset_b => {
|
||||
for (self.scratch.only_in_b_static_dispatch_constraints.sliceRange(partitioned.only_in_b)) |only_b| {
|
||||
self.types_store.static_dispatch_constraints.items.appendAssumeCapacity(only_b);
|
||||
}
|
||||
},
|
||||
.b_subset_a => {
|
||||
for (self.scratch.only_in_a_static_dispatch_constraints.sliceRange(partitioned.only_in_a)) |only_a| {
|
||||
self.types_store.static_dispatch_constraints.items.appendAssumeCapacity(only_a);
|
||||
}
|
||||
},
|
||||
for (self.scratch.only_in_a_static_dispatch_constraints.sliceRange(partitioned.only_in_a)) |only_a| {
|
||||
self.types_store.static_dispatch_constraints.items.appendAssumeCapacity(only_a);
|
||||
}
|
||||
for (self.scratch.only_in_b_static_dispatch_constraints.sliceRange(partitioned.only_in_b)) |only_b| {
|
||||
self.types_store.static_dispatch_constraints.items.appendAssumeCapacity(only_b);
|
||||
}
|
||||
|
||||
return self.types_store.static_dispatch_constraints.rangeToEnd(top);
|
||||
|
|
|
|||
|
|
@ -86,11 +86,6 @@ TYPE MISMATCH - Color.md:51:104:51:105
|
|||
TYPE DOES NOT HAVE METHODS - Color.md:22:15:22:26
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:29:13:29:26
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:35:17:35:41
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:36:21:36:45
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:37:21:37:45
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:38:21:38:45
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:39:21:39:45
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:40:21:40:45
|
||||
TYPE DOES NOT HAVE METHODS - Color.md:62:8:62:28
|
||||
# PROBLEMS
|
||||
**MODULE HEADER DEPRECATED**
|
||||
|
|
@ -204,56 +199,6 @@ You're trying to call the `is_char_in_hex_range` method on a `Num(Int(_size))`:
|
|||
|
||||
But `Num(Int(_size))` doesn't support methods.
|
||||
|
||||
**TYPE DOES NOT HAVE METHODS**
|
||||
You're trying to call the `is_char_in_hex_range` method on a `Num(Int(_size))`:
|
||||
**Color.md:36:21:36:45:**
|
||||
```roc
|
||||
and b.is_char_in_hex_range()
|
||||
```
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
But `Num(Int(_size))` doesn't support methods.
|
||||
|
||||
**TYPE DOES NOT HAVE METHODS**
|
||||
You're trying to call the `is_char_in_hex_range` method on a `Num(Int(_size))`:
|
||||
**Color.md:37:21:37:45:**
|
||||
```roc
|
||||
and c.is_char_in_hex_range()
|
||||
```
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
But `Num(Int(_size))` doesn't support methods.
|
||||
|
||||
**TYPE DOES NOT HAVE METHODS**
|
||||
You're trying to call the `is_char_in_hex_range` method on a `Num(Int(_size))`:
|
||||
**Color.md:38:21:38:45:**
|
||||
```roc
|
||||
and d.is_char_in_hex_range()
|
||||
```
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
But `Num(Int(_size))` doesn't support methods.
|
||||
|
||||
**TYPE DOES NOT HAVE METHODS**
|
||||
You're trying to call the `is_char_in_hex_range` method on a `Num(Int(_size))`:
|
||||
**Color.md:39:21:39:45:**
|
||||
```roc
|
||||
and e.is_char_in_hex_range()
|
||||
```
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
But `Num(Int(_size))` doesn't support methods.
|
||||
|
||||
**TYPE DOES NOT HAVE METHODS**
|
||||
You're trying to call the `is_char_in_hex_range` method on a `Num(Int(_size))`:
|
||||
**Color.md:40:21:40:45:**
|
||||
```roc
|
||||
and f.is_char_in_hex_range()
|
||||
```
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
But `Num(Int(_size))` doesn't support methods.
|
||||
|
||||
**TYPE DOES NOT HAVE METHODS**
|
||||
You're trying to call the `is_named_color` method on a `Str`:
|
||||
**Color.md:62:8:62:28:**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue