You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[buildable] hy-check(emit_guard): local binding shadowing package qualifier in qualified type not rejected (HYB-E1002, Row 13) #4004
Under GUARANTEES.md Row 13 (BUILDABLE), every source file accepted by hyb check must emit Go code that passes go build.
In compiler/crates/hy-check/src/emit_guard.rs, TypeExpr::Named checks whether a named type is shadowed by a local binding or parameter in the current environment: ust TypeExpr::Named { name, span } => { if let Some(st) = st { for tok in name.split(|c: char| !c.is_ascii_alphanumeric() && c != '_' && c != '.') { if !tok.is_empty() && st.env_lookup(tok).is_some() { // emit HYB-E1002 } } } }
Because the split predicate explicitly excludes '.' (c != '.'), any qualified type name such as sync.Mutex, os.File, or net.IP does NOT split at '.'. As a result, tok remains the entire string (e.g. "sync.Mutex"). Because locals and parameters cannot contain '.', st.env_lookup("sync.Mutex") is always None.
Consequently, when a local variable or parameter shadows an imported package name in a qualified type:
`hy
import "sync"
fn main() {
sync := 1
var m sync.Mutex
_ = sync
_ = m
} hyb check` accepts the file with 0 diagnostics (Verdict::Accept, exit 0).
The emitter reproduces the declaration verbatim into Go:
`go
package main
import "sync"
func main() {
sync := 1
var m sync.Mutex
_ = sync
_ = m
} Andgo buildfails compilation: ext
sync (variable) is not a package
`
violating Row 13 (BUILDABLE).
Fix & Verification
In compiler/crates/hy-check/src/emit_guard.rs:
Check whether the qualified head of TypeExpr::Named has a package qualifier (head.split_once('.')).
If the package qualifier matches an in-scope local or parameter in st.env_lookup, emit HYB-E1002 explaining that Go rejects {pkg} (variable) is not a package.
Check bare type names and generic type arguments inside [...] against st.env_lookup as before.
Added regression test suite compiler/crates/hy-check/tests/shadowed_qualified_type_package.rs verifying:
Local variables shadowing package names (sync := 1; var m sync.Mutex) reject with HYB-E1002.
Function parameters shadowing package names (fn f(os int) { var f os.File }) reject with HYB-E1002.
Shadowed generic type arguments reject with HYB-E1002.
Locals matching member names without shadowing the package (mutex := 1; var m sync.Mutex) accept cleanly.
Settlement Coordinates:
Author: @bradwue313
Base USDC: 0x38e0f40371be8b30f0909f890a1f701a25cd30cd
The bug stems from how TypeExpr::Named handles qualified type identifiers during environment validation in compiler/crates/hy-check/src/emit_guard.rs.
When checking named type expressions for local variable or parameter shadowing, the compiler tokenizes the type string using:
for tok in name.split(|c:char| !c.is_ascii_alphanumeric() && c != '_' && c != '.'){// ...
st.env_lookup(tok)}
Because the delimiter closure explicitly preserves '.', qualified types such as sync.Mutex are processed as a single monolithic token ("sync.Mutex"). Local variables and parameters cannot contain periods, so st.env_lookup("sync.Mutex") always resolves to None.
As a result, when a local binding shadows a package name (e.g., sync := 1), hyb check fails to catch the conflict. It emits the code verbatim into the Go output, causing go build to fail with sync (variable) is not a package, violating the BUILDABLE guarantee (Row 13).
Patch Implementation
To fix this, the validation logic must inspect the package qualifier (the segment preceding the first '.') independently of the full type string or type parameters.
// compiler/crates/hy-check/src/emit_guard.rsTypeExpr::Named{ name, span } => {ifletSome(st) = st {// Check if the qualified package prefix is shadowed by a local or parameterifletSome((pkg_qualifier, _)) = name.split_once('.'){if !pkg_qualifier.is_empty() && st.env_lookup(pkg_qualifier).is_some(){// Emit HYB-E1002: local binding shadows package qualifier in qualified type
ctx.emit_error(HYB_E1002,*span,format!("local binding `{}` shadows package name", pkg_qualifier));}}// Check remaining identifiers (e.g., generic type arguments or bare names)for tok in name.split(|c:char| !c.is_ascii_alphanumeric() && c != '_' && c != '.'){if !tok.is_empty() && st.env_lookup(tok).is_some(){
ctx.emit_error(HYB_E1002,*span,format!("local binding `{}` shadows type identifier", tok));}}}}
Regression Test Coverage
Add compiler/crates/hy-check/tests/shadowed_qualified_type_package.rs to enforce compile-time rejection of shadowed package qualifiers:
#[test]fntest_local_shadows_package_qualified_type(){let source = r#" import "sync" fn main() { sync := 1 var m sync.Mutex _ = sync _ = m } "#;let diagnostics = hy_check::check_str(source);assert_eq!(diagnostics.iter().filter(|d| d.code == "HYB-E1002").count(),1);}#[test]fntest_parameter_shadows_package_qualified_type(){let source = r#" import "os" fn f(os: int) { var file os.File _ = os _ = file } "#;let diagnostics = hy_check::check_str(source);assert_eq!(diagnostics.iter().filter(|d| d.code == "HYB-E1002").count(),1);}#[test]fntest_unshadowed_member_name_accepts(){let source = r#" import "sync" fn main() { mutex := 1 var m sync.Mutex _ = mutex _ = m } "#;let diagnostics = hy_check::check_str(source);assert!(diagnostics.is_empty());}
The core soundness violation stems from how compiler/crates/hy-check/src/emit_guard.rs processes TypeExpr::Named against local symbol environments (st).
When analyzing a named type expression, the emitter splits the type name string on non-alphanumeric, non-underscore characters, explicitly excluding '.' (c != '.'). For qualified type paths like sync.Mutex, the split operation treats the entire string as a single token ("sync.Mutex"). Because local bindings and function parameters cannot contain dots, environment lookups (st.env_lookup("sync.Mutex")) always return None.
Consequently, when a local variable or parameter shadows an imported package name (e.g., sync := 1), hyb check fails to detect the collision, emits the unconstrained Go code verbatim, and causes downstream compilation failure (sync (variable) is not a package), violating the GUARANTEES.md BUILDABLE invariant (Row 13).
Code Solution & Patch
To fix this, we must inspect the package qualifier prefix (the head before the first '.') of qualified type expressions, and check if that head targets a local symbol table entry rather than an imported package namespace.
// compiler/crates/hy-check/src/emit_guard.rsTypeExpr::Named{ name, span } => {ifletSome(st) = st {// Check for qualified type package shadowing (e.g., sync.Mutex where sync is a local)ifletSome((pkg_qualifier, _)) = name.split_once('.'){if !pkg_qualifier.is_empty() && st.env_lookup(pkg_qualifier).is_some(){// Emit HYB-E1002: local binding shadows package qualifieremit_diagnostic(
span,"HYB-E1002",&format!("local binding '{}' shadows package qualifier in type '{}'", pkg_qualifier, name),);}}// Existing token-based checks for bare types and generic argumentsfor tok in name.split(|c:char| !c.is_ascii_alphanumeric() && c != '_' && c != '.'){if !tok.is_empty() && st.env_lookup(tok).is_some(){// emit HYB-E1002}}}}
Regression Test Coverage
Add compiler/crates/hy-check/tests/shadowed_qualified_type_package.rs to validate the diagnostics pipeline:
#[test]fntest_local_shadows_package_qualified_type(){let source = r#" import "sync" fn main() { sync := 1 var m sync.Mutex _ = sync _ = m } "#;let result = hy_check::check(source);assert!(result.has_diagnostic("HYB-E1002"));}#[test]fntest_parameter_shadows_package_qualified_type(){let source = r#" import "os" fn f(os int) { var file os.File _ = os _ = file } "#;let result = hy_check::check(source);assert!(result.has_diagnostic("HYB-E1002"));}#[test]fntest_non_shadowing_member_access_accepted(){let source = r#" import "sync" fn main() { mutex := 1 var m sync.Mutex _ = mutex _ = m } "#;let result = hy_check::check(source);assert_eq!(result.verdict(),Verdict::Accept);}
Activity
dukenordj-rgb commentedon Nov 7, 2026
Root Cause Analysis
The bug stems from how
TypeExpr::Namedhandles qualified type identifiers during environment validation incompiler/crates/hy-check/src/emit_guard.rs.When checking named type expressions for local variable or parameter shadowing, the compiler tokenizes the type string using:
Because the delimiter closure explicitly preserves
'.', qualified types such assync.Mutexare processed as a single monolithic token ("sync.Mutex"). Local variables and parameters cannot contain periods, sost.env_lookup("sync.Mutex")always resolves toNone.As a result, when a local binding shadows a package name (e.g.,
sync := 1),hyb checkfails to catch the conflict. It emits the code verbatim into the Go output, causinggo buildto fail withsync (variable) is not a package, violating the BUILDABLE guarantee (Row 13).Patch Implementation
To fix this, the validation logic must inspect the package qualifier (the segment preceding the first
'.') independently of the full type string or type parameters.Regression Test Coverage
Add
compiler/crates/hy-check/tests/shadowed_qualified_type_package.rsto enforce compile-time rejection of shadowed package qualifiers:dukenordj-rgb commentedon Nov 7, 2026
Root Cause Analysis
The core soundness violation stems from how
compiler/crates/hy-check/src/emit_guard.rsprocessesTypeExpr::Namedagainst local symbol environments (st).When analyzing a named type expression, the emitter splits the type name string on non-alphanumeric, non-underscore characters, explicitly excluding
'.'(c != '.'). For qualified type paths likesync.Mutex, the split operation treats the entire string as a single token ("sync.Mutex"). Because local bindings and function parameters cannot contain dots, environment lookups (st.env_lookup("sync.Mutex")) always returnNone.Consequently, when a local variable or parameter shadows an imported package name (e.g.,
sync := 1),hyb checkfails to detect the collision, emits the unconstrained Go code verbatim, and causes downstream compilation failure (sync (variable) is not a package), violating the GUARANTEES.md BUILDABLE invariant (Row 13).Code Solution & Patch
To fix this, we must inspect the package qualifier prefix (the head before the first
'.') of qualified type expressions, and check if that head targets a local symbol table entry rather than an imported package namespace.Regression Test Coverage
Add
compiler/crates/hy-check/tests/shadowed_qualified_type_package.rsto validate the diagnostics pipeline: