-
Notifications
You must be signed in to change notification settings - Fork 1
v0.4.1: Destructuring Params, Circular Import Diagnostics, Otherwise Lint #21
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 1 commit
151d592
5ea6cc7
767fa1f
e24562d
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 | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6339,7 +6339,12 @@ impl Interpreter { | |||||||||||||||||||||||||||
| // Should not reach here due to arity check above | ||||||||||||||||||||||||||||
| Value::Unit | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| func_env.borrow_mut().define(param.name.clone(), value); | ||||||||||||||||||||||||||||
| func_env | ||||||||||||||||||||||||||||
| .borrow_mut() | ||||||||||||||||||||||||||||
| .define(param.name.clone(), value.clone()); | ||||||||||||||||||||||||||||
| if let Some(ref pat) = param.pattern { | ||||||||||||||||||||||||||||
| self.bind_pattern(pat, &value)?; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| func_env | |
| .borrow_mut() | |
| .define(param.name.clone(), value.clone()); | |
| if let Some(ref pat) = param.pattern { | |
| self.bind_pattern(pat, &value)?; | |
| } | |
| // Bind any destructuring pattern before moving the value into the environment | |
| if let Some(ref pat) = param.pattern { | |
| self.bind_pattern(pat, &value)?; | |
| } | |
| func_env | |
| .borrow_mut() | |
| .define(param.name.clone(), value); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -307,7 +307,15 @@ impl Parser { | |
|
|
||
| if !self.check(&TokenKind::RightParen) { | ||
| loop { | ||
| let name = self.consume_identifier("Expected parameter name")?; | ||
| let (name, pattern) = | ||
| if self.check(&TokenKind::LeftBrace) || self.check(&TokenKind::LeftBracket) { | ||
| let pat = self.parse_pattern()?; | ||
| let synth_name = format!("_destructure_{}", params.len()); | ||
| (synth_name, Some(pat)) | ||
| } else { | ||
| let name = self.consume_identifier("Expected parameter name")?; | ||
| (name, None) | ||
|
Comment on lines
+311
to
+317
|
||
| }; | ||
|
|
||
| let type_annotation = if self.match_token(&[TokenKind::Colon]) { | ||
| Some(self.parse_type()?) | ||
|
|
@@ -325,6 +333,7 @@ impl Parser { | |
| name, | ||
| type_annotation, | ||
| default, | ||
| pattern, | ||
| }); | ||
|
|
||
| if !self.match_token(&[TokenKind::Comma]) { | ||
|
|
@@ -522,6 +531,7 @@ impl Parser { | |
| name: param_name, | ||
| type_annotation, | ||
| default: None, | ||
| pattern: None, | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -744,10 +744,14 @@ impl TypeContext { | |||||||||||||||
|
|
||||||||||||||||
| // When otherwise is present, unwrap Result<T,E> -> T or Option<T> -> T | ||||||||||||||||
| let inferred = if let Some(otherwise_block) = otherwise { | ||||||||||||||||
| // Check that the otherwise block diverges | ||||||||||||||||
| // Check that the otherwise block diverges. | ||||||||||||||||
| // This is an error, not a warning: non-diverging otherwise blocks | ||||||||||||||||
| // always crash at runtime ("otherwise block must diverge"), so | ||||||||||||||||
| // catching this at lint time prevents production outages. | ||||||||||||||||
| // See Finding #76: production outage from silent runtime error. | ||||||||||||||||
| if !self.block_diverges(otherwise_block) { | ||||||||||||||||
| let line = self.find_line_near("otherwise"); | ||||||||||||||||
| self.warning( | ||||||||||||||||
| self.error( | ||||||||||||||||
| "otherwise block does not diverge — it must end with return, break, or continue".to_string(), | ||||||||||||||||
| line, | ||||||||||||||||
| Some("Add a return, break, or continue statement".to_string()), | ||||||||||||||||
|
|
@@ -838,7 +842,7 @@ impl TypeContext { | |||||||||||||||
| if self.strict_lint { | ||||||||||||||||
| let fn_line = self.find_line_near(&format!("fn {}", name)); | ||||||||||||||||
| for param in params { | ||||||||||||||||
| if param.type_annotation.is_none() { | ||||||||||||||||
| if param.type_annotation.is_none() && param.pattern.is_none() { | ||||||||||||||||
| self.emit_with_kind( | ||||||||||||||||
| Severity::Warning, | ||||||||||||||||
| DiagnosticKind::MissingParamAnnotation, | ||||||||||||||||
|
|
@@ -869,7 +873,10 @@ impl TypeContext { | |||||||||||||||
| .as_ref() | ||||||||||||||||
| .map(|t| self.resolve_type_expr(t)) | ||||||||||||||||
| .unwrap_or(Type::Any); | ||||||||||||||||
| self.bind(¶m.name, typ); | ||||||||||||||||
| self.bind(¶m.name, typ.clone()); | ||||||||||||||||
|
||||||||||||||||
| self.bind(¶m.name, typ.clone()); | |
| // For destructured parameters, only bind the pattern variables, not the | |
| // synthetic parameter name (e.g., `_destructure_0`), to avoid leaking | |
| // internal names into the function scope. | |
| if param.pattern.is_none() { | |
| self.bind(¶m.name, typ.clone()); | |
| } |
Outdated
Copilot
AI
Mar 11, 2026
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.
Test name is now misleading: test_otherwise_without_return_warns asserts an Error diagnostic (not a warning). Renaming the test to reflect the new behavior will make failures easier to interpret.
| fn test_otherwise_without_return_warns() { | |
| fn test_otherwise_without_return_errors() { |
Outdated
Copilot
AI
Mar 11, 2026
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.
This test comment says it uses a “properly diverging otherwise” to isolate gradual typing, but the test body no longer contains an otherwise at all. Update the comment to match the current test intent (or add the diverging otherwise back if that was the intended coverage).
| // Gradual typing: untyped code produces no type errors. | |
| // Note: a non-diverging otherwise block is now an error (not a type error), | |
| // so we use a properly diverging otherwise to isolate the gradual typing check. | |
| // Gradual typing: untyped code (including unannotated lambdas) produces no type errors. |
Outdated
Copilot
AI
Mar 11, 2026
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.
The comment says this test uses a “properly diverging otherwise” to isolate gradual typing, but the sample code no longer includes an otherwise at all. Update or remove the comment to match the current fixture so the test intent stays clear.
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.
Function-call parameter binding defines the synthetic "_destructure_N" parameter in the function environment even when the parameter is a destructuring pattern. This makes an internal implementation detail user-visible (it can be referenced from the function body) and can also collide with user-chosen parameter names (e.g., a user explicitly naming a param
_destructure_0). Consider skippingdefine(param.name, ...)whenparam.pattern.is_some()(bind only the pattern vars), or use a non-lexable internal name and ensure it’s never bound in the runtime environment.