-
Notifications
You must be signed in to change notification settings - Fork 12
fix(expr): use NULLTYPE for empty flatten result instead of INT #316
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
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 |
|---|---|---|
|
|
@@ -67,7 +67,10 @@ pub fn flatten_fn(ctx: Ctx, a: &[ExprValue]) -> R { | |
| } | ||
| } | ||
| let et = if result.is_empty() { | ||
| ExprType::INT | ||
| // An empty flatten result has no elements to infer a type from. | ||
| // Use NULLTYPE (the bottom type for lists) so the result is accepted by | ||
| // any function that declares a list[nulltype] overload (e.g. repr_sh). | ||
| ExprType::NULLTYPE | ||
|
Contributor
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. The fix is correct and strictly widens what downstream dispatch accepts (every concrete list overload that could previously match 1. The element type is still discarded when it is recoverable.
Deriving from the input keeps runtime types consistent with the signature and makes let et = if let Some(first) = result.first() {
first.expr_type()
} else {
// Recover T1 from the input: list[list[T1]] -> T1, list[T1] -> T1.
let input_elem = a[0].list_elem_type();
match &input_elem {
Some(t) => t.list_element_type().cloned().unwrap_or(ExprType::NULLTYPE),
None => ExprType::NULLTYPE,
}
};2.
Contributor
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. The new comment cites
The same "empty list literal" framing is repeated for After this change that characterization is no longer accurate. This is a separate drift from the Per AGENTS.md ("before committing, always confirm the spec and code line up"), suggest widening the wording in the same commit, e.g.:
|
||
| } else { | ||
| result[0].expr_type() | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1714,3 +1714,41 @@ fn empty_listcomp_type_is_nulltype() { | |
| "list[nulltype]" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn flatten_empty_comprehension_has_nulltype() { | ||
| // Regression: flatten over an empty comprehension result must yield list[nulltype], | ||
| // not list[int], so that functions like repr_sh (which has a list[nulltype] overload) | ||
| // accept the result. | ||
| let mut st = SymbolTable::new(); | ||
| st.set( | ||
| "Env", | ||
| ExprValue::make_list(vec![], ExprType::STRING).unwrap(), | ||
| ) | ||
| .unwrap(); | ||
| let parsed = | ||
| openjd_expr::ParsedExpression::new("repr_sh(flatten([[\"-e\", e] for e in Env]))").unwrap(); | ||
| let result = parsed.evaluate(&st).unwrap(); | ||
| // repr_sh of an empty list produces an empty string | ||
| assert_eq!(result.to_display_string(), ""); | ||
|
Contributor
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. Neither new test actually asserts the type, despite both test names claiming to ( The sibling test just above ( assert_eq!(
eval("flatten([[\"-e\", e] for e in []])").expr_type().to_string(),
"list[nulltype]"
);Separately, |
||
| } | ||
|
|
||
| #[test] | ||
| fn flatten_nonempty_comprehension_has_string_type() { | ||
| // Non-empty case: flatten over a string comprehension must still yield list[string]. | ||
| let mut st = SymbolTable::new(); | ||
| st.set( | ||
| "Env", | ||
| ExprValue::make_list(vec![ExprValue::String("A=1".to_string())], ExprType::STRING).unwrap(), | ||
| ) | ||
| .unwrap(); | ||
| let parsed = | ||
| openjd_expr::ParsedExpression::new("repr_sh(flatten([[\"-e\", e] for e in Env]))").unwrap(); | ||
| let result = parsed.evaluate(&st).unwrap(); | ||
| // repr_sh on a list of strings should produce shell-escaped space-separated values | ||
| assert!( | ||
| result.to_display_string().contains("-e"), | ||
| "expected repr_sh output to contain -e, got: {}", | ||
| result.to_display_string() | ||
| ); | ||
| } | ||
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 change makes
ExprType::NULLTYPEa livehint_typeintomake_listfrom a production code path, and the spec documenting that hint is currently wrong about what it produces.specs/expr/values.md"Empty list variant selection byhint_type" (around line 186) lists only BOOL / INT / FLOAT / PATH / LIST[T] and thenanything else → ListInt([]) (canonical empty list). The actual code invalue.rs:499-517mapsNullType → ListList([], NULLTYPE, 0)and the_fallback likewise toListList([], NULLTYPE, 0)— notListInt([]). The table is also missing theSTRING → ListString([], 0)row.The drift is pre-existing, but this PR is what makes the
NULLTYPErow load-bearing: a reader following the spec would concludeflattenof an empty list yieldsListInt([])/list[int], i.e. exactly the bug being fixed. Per AGENTS.md ("before committing, always confirm the spec and code line up"), worth correcting the table in this commit: