Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/pcb-zen-core/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,8 +701,13 @@ impl ModuleConverter {
}

fn is_instance_position(&self, key: &str, instance_ref: &InstanceRef) -> Option<()> {
// Strip @U suffix from the key if present (for multi-unit symbols)
// e.g., "U1.OPEN_Q_6490CS@U1" -> "U1.OPEN_Q_6490CS"
let key_without_unit = key.split('@').next().unwrap_or(key);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should disallow @ in component names as part of this too, which I don't think should break anything

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, could you add this to validate_identifier_name() in lang/validation.rs?


// Traverse the instance hierarchy using the dot-separated key
key.split('.')
key_without_unit
.split('.')
.try_fold(instance_ref, |current_ref, part| {
self.schematic
.instances
Expand Down
41 changes: 36 additions & 5 deletions crates/pcb-zen-core/src/lang/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ pub enum ValidationError {
EmptyName { context: String },
#[error("{context} cannot contain whitespace. Got: {name:?}")]
NameContainsWhitespace { context: String, name: String },
#[error("{context} cannot contain dots. Got: {name:?}")]
NameContainsDots { context: String, name: String },
#[error("{context} cannot contain invalid characters {invalid_chars:?}. Got: {name:?}")]
NameContainsInvalidChars {
context: String,
name: String,
invalid_chars: String,
},
#[error("{context} must contain only ASCII characters. Got: {name:?}")]
NameNotAscii { context: String, name: String },
}
Expand Down Expand Up @@ -45,9 +49,10 @@ pub fn validate_identifier_name(name: &str, context: &str) -> Result<(), Validat

// Check for dots (confusing for hierarchical references)
if name.contains('.') {
return Err(ValidationError::NameContainsDots {
return Err(ValidationError::NameContainsInvalidChars {
context: context.to_string(),
name: name.to_string(),
invalid_chars: ".".to_string(),
});
}

Expand All @@ -59,6 +64,15 @@ pub fn validate_identifier_name(name: &str, context: &str) -> Result<(), Validat
});
}

// Check for @ sign
if name.contains('@') {
return Err(ValidationError::NameContainsInvalidChars {
context: context.to_string(),
name: name.to_string(),
invalid_chars: "@".to_string(),
});
}

Ok(())
}

Expand All @@ -84,7 +98,6 @@ mod tests {
"power-rail",
"VCC+", // Plus signs allowed
"GND-", // Minus signs allowed
"R@1", // @ signs allowed
"C#1", // # signs allowed
"L$1", // $ signs allowed
"Q&1", // & signs allowed
Expand Down Expand Up @@ -134,6 +147,23 @@ mod tests {
}
}

#[test]
fn test_invalid_names_with_at_sign() {
let invalid_names = vec!["R@1", "LED@STATUS", "power@rail"];
for name in invalid_names {
let result = validate_identifier_name(name, "Test name");
assert!(result.is_err(), "Expected '{}' to be invalid", name);
let error_msg = format!("{}", result.unwrap_err());
assert!(
error_msg.contains("cannot contain invalid characters")
&& error_msg.contains("\"@\""),
"Expected @ sign error for '{}', got: {}",
name,
error_msg
);
}
}

#[test]
fn test_invalid_names_with_dots() {
let invalid_names = vec![
Expand All @@ -150,7 +180,8 @@ mod tests {
assert!(result.is_err(), "Expected '{}' to be invalid", name);
let error_msg = format!("{}", result.unwrap_err());
assert!(
error_msg.contains("cannot contain dots"),
error_msg.contains("cannot contain invalid characters")
&& error_msg.contains("\".\""),
"Expected dot error for '{}', got: {}",
name,
error_msg
Expand Down