Skip to content
Open
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: 3 additions & 4 deletions pyrefly/lib/alt/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use ruff_python_ast::name::Name;
use ruff_text_size::TextRange;
use starlark_map::small_set::SmallSet;
use vec1::Vec1;
use vec1::vec1;

use crate::alt::answers::LookupAnswer;
use crate::alt::answers_solver::AnswersSolver;
Expand Down Expand Up @@ -614,7 +613,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
} else if !error_messages.is_empty() {
error_messages.sort();
error_messages.dedup();
let mut msg = vec1![error_messages.join("\n")];
let header = error_messages.remove(0);
let mut details = error_messages;
// Skip suggestions when we have a partial union failure to avoid suggesting
// attributes from the types that have them when the problem is that some types
// don't have the attribute at all.
Expand All @@ -623,9 +623,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
.as_ref()
.and_then(|attr_base| self.suggest_attribute_name(attr_name, attr_base))
{
msg.push(format!("Did you mean `{suggestion}`?"));
details.push(format!("Did you mean `{suggestion}`?"));
}
let (header, details) = msg.split_off_first();
errors
.error_builder(range, ErrorKind::MissingAttribute, header)
.with_details(details)
Expand Down
21 changes: 21 additions & 0 deletions pyrefly/lib/error/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ impl Error {
error_kind: ErrorKind,
) -> Self {
let display_range = module.display_range(range);
assert!(
!header.contains(['\n', '\r']),
"error header must not contain newlines"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should include the error kind in this message, so that assertion failures are easier to debug.

);
let msg_header = header.into_boxed_str();
let msg_details = if details.is_empty() {
None
Expand Down Expand Up @@ -585,6 +589,23 @@ mod tests {
);
}

#[test]
#[should_panic(expected = "error header must not contain newlines")]
fn test_error_header_rejects_newlines() {
let module_info = Module::new(
ModuleName::from_str("test"),
ModulePath::filesystem(PathBuf::from("test.py")),
Arc::new("x = 1".to_owned()),
);
let _ = Error::new(
module_info,
TextRange::new(TextSize::new(0), TextSize::new(1)),
"first line\r\nsecond line".to_owned(),
Vec::new(),
ErrorKind::BadReturn,
);
}

/// Integration test: verify that binary operator errors from the type checker
/// produce secondary annotations labeling both operands with their types.
#[test]
Expand Down
Loading