Repository navigation
fix: bugs found analysing the advent-of-code-ndc solutions 🎄 #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
096dab5
9ed9471
16839d7
7d53248
849bf4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -126,10 +126,18 @@ impl Analyser { | |
| }) | ||
| .collect::<Vec<_>>(); | ||
| for waiting in &scheme { | ||
| if waiting.origin.is_none() | ||
| && let Constraint::Call(call) = &waiting.constraint | ||
| { | ||
| self.pick_for_any_arguments(call); | ||
| match &waiting.constraint { | ||
| Constraint::Call(call) if waiting.origin.is_none() => { | ||
| self.pick_for_any_arguments(call); | ||
| } | ||
| // The body compiled for arguments of type `Any` can't tell | ||
| // which overload a function value means, so the program | ||
| // picks as it calls it. | ||
| Constraint::FunctionValue(value) if waiting.origin.is_none() => { | ||
| let binding = self.every_overload(value); | ||
| self.picks.bindings.insert(value.id(), binding); | ||
|
Comment on lines
+136
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a generic function is used as a value, its shared body now passes an Useful? React with 👍 / 👎.
Comment on lines
+136
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a deferred function value contains locally declared overloads and escapes into another function, the emitted Useful? React with 👍 / 👎. |
||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
| // A value of type `Any` that the body passes where a type parameter | ||
|
|
@@ -209,15 +217,35 @@ impl Analyser { | |
| /// The generic function's variables that `constraint` settles once it's | ||
| /// solved. A function passed to a call gets the types the call passes | ||
| /// it, as `fn(a, x) => a + x` in `xs.fold(0, fn(a, x) => a + x)`, so | ||
| /// they count as settled by the call too. | ||
| /// they count as settled by the call too, and so does an overloaded | ||
| /// function's name passed to one, as `signum` in `xs.map(signum)`, | ||
| /// whose type is what the call expects of it. | ||
| fn generic_outputs(&self, constraint: &Constraint) -> Vec<TypeVar> { | ||
| let passed_functions = match constraint { | ||
| Constraint::Call(call) => call | ||
| .arguments() | ||
| .iter() | ||
| .map(|argument| self.vars.resolve(argument)) | ||
| .filter(|argument| matches!(argument, StaticType::Function { .. })) | ||
| .collect(), | ||
| Constraint::Call(call) => { | ||
| let values = self | ||
| .waiting | ||
| .values() | ||
| .filter_map(|waiting| match &waiting.constraint { | ||
| Constraint::FunctionValue(value) => { | ||
| Some(self.vars.resolve(value.expected())) | ||
| } | ||
| _ => None, | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
| call.arguments() | ||
| .iter() | ||
| .map(|argument| self.vars.resolve(argument)) | ||
| .filter(|argument| { | ||
| matches!(argument, StaticType::Function { .. }) || values.contains(argument) | ||
| }) | ||
| .collect() | ||
| } | ||
| // Picking the overload settles what the value returns. | ||
| Constraint::FunctionValue(value) => match self.vars.resolve(value.expected()) { | ||
| StaticType::Function { return_type, .. } => vec![*return_type], | ||
| _ => Vec::new(), | ||
| }, | ||
| _ => Vec::new(), | ||
| }; | ||
| constraint | ||
|
|
@@ -232,8 +260,8 @@ impl Analyser { | |
| /// The generic function's variables that its constraints wait on but | ||
| /// that its parameters don't decide, by constraint, oldest first. What | ||
| /// a constraint settles is decided when what it waits on is. An | ||
| /// overloaded function's name used as a value has to be decided in the | ||
| /// body, which is compiled once, so what it waits on counts too. | ||
| /// overloaded function's name used as a value is picked like a call: | ||
| /// for each instance, from the types its parameters decide. | ||
| fn ambiguous_vars(&self, parameters: &[StaticType]) -> Vec<(u64, Vec<TypeVar>)> { | ||
| let serials = self.generic_constraints(); | ||
| let mut decided = parameters | ||
|
|
@@ -245,11 +273,10 @@ impl Analyser { | |
| let mut changed = false; | ||
| for serial in &serials { | ||
| let constraint = &self.waiting[serial].constraint; | ||
| if matches!(constraint, Constraint::FunctionValue(_)) | ||
| || !self | ||
| .generic_inputs(constraint) | ||
| .iter() | ||
| .all(|var| decided.contains(var)) | ||
| if !self | ||
| .generic_inputs(constraint) | ||
| .iter() | ||
| .all(|var| decided.contains(var)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
@@ -265,14 +292,11 @@ impl Analyser { | |
| .into_iter() | ||
| .filter_map(|serial| { | ||
| let constraint = &self.waiting[&serial].constraint; | ||
| let ambiguous = match constraint { | ||
| Constraint::FunctionValue(_) => self.generic_inputs(constraint), | ||
| _ => self | ||
| .generic_inputs(constraint) | ||
| .into_iter() | ||
| .filter(|var| !decided.contains(var)) | ||
| .collect(), | ||
| }; | ||
| let ambiguous = self | ||
| .generic_inputs(constraint) | ||
| .into_iter() | ||
| .filter(|var| !decided.contains(var)) | ||
| .collect::<Vec<_>>(); | ||
| (!ambiguous.is_empty()).then_some((serial, ambiguous)) | ||
| }) | ||
| .collect() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -817,6 +817,18 @@ impl TypeVars { | |
| // n = a` is still an Int. | ||
| (StaticType::Var(var), StaticType::Any) if self.is_literal(*var) => true, | ||
| (StaticType::Iterable(_), StaticType::Var(var)) if !self.is_literal(*var) => true, | ||
| // A literal passed for an `Iterable<T>` is the sequence it holds, | ||
| // as `["a"]` passed to `len` is a `List<String>`: only a | ||
| // parameter is ever an `Iterable` itself. | ||
| (StaticType::Iterable(_), StaticType::Var(var)) => { | ||
| let Some(held) = self | ||
| .literal_defaults(*var) | ||
| .and_then(|defaults| defaults.into_iter().next()) | ||
| else { | ||
| return true; | ||
|
Comment on lines
+827
to
+828
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the argument's literal variable holds another unsettled literal, Useful? React with 👍 / 👎. |
||
| }; | ||
| self.settle_literal(*var, &held) && self.bind_argument(¶meter, &held) | ||
| } | ||
| (StaticType::Var(_), _) | (_, StaticType::Var(_)) => self.unify(¶meter, &argument), | ||
| (_, StaticType::Any) => true, | ||
| ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a shared generic body passes a deferred overload value to a
pure fn, memoization hashes that value before executing the callback, butObject::hashstill panics for everyOverloadSet. For example,pure fn invoke(g, x) => g(x); fn apply(x) => invoke(abs, x); let h: Fn(Int) -> Int = apply; print(h(1));now passes analysis but reaches the Rust panicOverloadSet cannot be used as a map keyinVm::dispatch_call, rather than producing1. Direct specialized calls avoid this representation, so they do not cover the failure. Give deferred function values a supported hash/identity representation, or resolve them before the memoization cache hashes arguments.Useful? React with 👍 / 👎.