Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
a2fdc89
Change string formatting in Display implementation
Karman-singh15 Nov 21, 2025
26e462d
feat: Improve LSP literal completions by removing quotes when inserti…
Karman-singh15 Nov 22, 2025
bc16325
Merge branch 'facebook:main' into main
Karman-singh15 Nov 22, 2025
44c9c09
test: add 'inserting' field to expected LSP completion results for li…
Karman-singh15 Nov 22, 2025
61847f4
combined the quotes test in completion.rs and remove a no-op
Karman-singh15 Nov 24, 2025
35e07a2
Fix linter warnings for cargo
stroxler Nov 22, 2025
58798ba
Just pass down inferred_from_method as a boolean flag
stroxler Nov 22, 2025
285379f
Combine the top to matches of `calculate_class_field`
stroxler Nov 22, 2025
f4541ac
Inline `check_and_sanitize_type_parameters` into the main match
stroxler Nov 22, 2025
b727739
Track a bug in class field sanitization
stroxler Nov 22, 2025
3012d71
Inline get_class_field_initialization
stroxler Nov 22, 2025
309300c
Inline magic initialization check into the match
stroxler Nov 22, 2025
e16f11f
Get rid of magic initialization for instance methods
stroxler Nov 22, 2025
ea4270e
Trivial restructure of matching code
stroxler Nov 22, 2025
9b549c9
Extract `dataclass_field_initialization` code to a helper
stroxler Nov 22, 2025
f04479a
Vendor typify
Nov 22, 2025
c78f732
preserve quantified information when unpacking quantified type var tuple
yangdanny97 Nov 23, 2025
005801c
Add more info to an `unreachable!` message
rchen152 Nov 24, 2025
59b94ca
Add OutputWithLocations struct.
Nov 24, 2025
d0f3220
Add mock implementation of OutputWithLocations
Nov 24, 2025
7081e24
Update pyrefly version]
Nov 24, 2025
7f4cc24
Implement write_str
Nov 24, 2025
4a2fa2a
Implement write_qname
Nov 24, 2025
ed7a356
Implement write_lit
Nov 24, 2025
4d42f7b
Implement write_targs
Nov 24, 2025
6db8cdf
fix parsing of multi-line parameter descriptions in docstrings #1588 …
asukaminato0721 Nov 24, 2025
3f00984
Implement write_type.
Nov 24, 2025
b242eed
Test that Union types do not split properly.
Nov 24, 2025
e5bf11e
fix unused variable detection for reassignments
kinto0 Nov 24, 2025
4e02534
filter star imports from unused detection
kinto0 Nov 24, 2025
7c365fa
highlight false negative
kinto0 Nov 24, 2025
aeb0c55
bump version
kinto0 Nov 24, 2025
32cfc95
Add test to show the Intersection types do not split properly.
Nov 24, 2025
29bfa47
Create fmt_type_sequence helper function to handle types with separat…
Nov 24, 2025
7aa0e90
Update Union Logic to properly split into parts.
Nov 24, 2025
11c7137
Update intersection types to properly split into parts.
Nov 24, 2025
1ff453d
Add test showing that tuple type does not have a location.
Nov 24, 2025
deb5c23
Add test showing that types from typing.py do not have locations.
Nov 24, 2025
394a6fa
Fix crashes when `yield` or `yield from` appears in type annotations
grievejia Nov 24, 2025
143a3b0
add tests for last diff
kinto0 Nov 24, 2025
7c98ce6
refactor: update `get_callables_from_call` destructuring to include a…
Karman-singh15 Nov 24, 2025
ad4e361
Refactor add_literal_completions to remove in_string check
Karman-singh15 Nov 24, 2025
38fcb67
Merge branch 'facebook:main' into main
Karman-singh15 Nov 24, 2025
b2fe8ee
Merge branch 'facebook:main' into main
Karman-singh15 Nov 25, 2025
64a4af2
added missing call
Karman-singh15 Nov 25, 2025
a4f50d4
updated the duplicate quote test
Karman-singh15 Nov 27, 2025
1851ee7
Merge branch 'main' into main
Karman-singh15 Nov 27, 2025
935f9bc
add optional display name to Type::Union
yangdanny97 Nov 25, 2025
65aef3d
bump ruff packages
yangdanny97 Nov 26, 2025
acc00ad
feat: Improve LSP literal completions by removing quotes when inserti…
Karman-singh15 Nov 22, 2025
f00a366
test: add 'inserting' field to expected LSP completion results for li…
Karman-singh15 Nov 22, 2025
5c8d4f9
Fix linter warnings for cargo
stroxler Nov 22, 2025
c781bfa
Just pass down inferred_from_method as a boolean flag
stroxler Nov 22, 2025
3e803c5
Inline `check_and_sanitize_type_parameters` into the main match
stroxler Nov 22, 2025
5f28775
Inline get_class_field_initialization
stroxler Nov 22, 2025
10ac53c
preserve quantified information when unpacking quantified type var tuple
yangdanny97 Nov 23, 2025
13bb911
Add OutputWithLocations struct.
Nov 24, 2025
6e7d7cb
Update pyrefly version]
Nov 24, 2025
63f86cb
fix parsing of multi-line parameter descriptions in docstrings #1588 …
asukaminato0721 Nov 24, 2025
aa455f5
Implement write_type.
Nov 24, 2025
b26bb7e
fix unused variable detection for reassignments
kinto0 Nov 24, 2025
ef09f28
filter star imports from unused detection
kinto0 Nov 24, 2025
ebadfed
Update Union Logic to properly split into parts.
Nov 24, 2025
08dfad0
Add test showing that tuple type does not have a location.
Nov 24, 2025
6e4267d
Fix crashes when `yield` or `yield from` appears in type annotations
grievejia Nov 24, 2025
02b24ba
add tests for last diff
kinto0 Nov 24, 2025
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
17 changes: 14 additions & 3 deletions pyrefly/lib/error/expectation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ impl Expectation {
))
} else {
for (line_no, msg) in &self.error {
if !errors.iter().any(|e| {
e.msg().replace("\n", "\\n").contains(msg)
// Compare the raw message and a normalized form to tolerate small
// formatting changes (escaped quotes vs doubled quotes).
let found = errors.iter().any(|e| {
let raw = e.msg().replace("\n", "\\n");
let norm = normalize_message(&raw);
(raw.contains(msg) || norm.contains(msg))
&& e.display_range().start.line_within_file().get() as usize == *line_no
}) {
});
if !found {
return Err(anyhow::anyhow!(
"Expectations failed for {}: can't find error (line {line_no}): {msg}",
self.module.path()
Expand All @@ -57,3 +62,9 @@ impl Expectation {
}
}
}

fn normalize_message(s: &str) -> String {
s.replace("\\'", "'") // unescape single quotes
.replace("\"", "\"") // keep double-quote escapes as-is
Copy link
Contributor

Choose a reason for hiding this comment

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

is it worth including this line? let's keep the comment but remove the no-op

Copy link
Author

Choose a reason for hiding this comment

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

sure i will remove the line 68 but leave the comment there?
is there a problem with the code tho?

Copy link
Contributor

Choose a reason for hiding this comment

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

no issue with the code. but replacing something with itself (a no-op) isn't worth doing here imo

Copy link
Author

Choose a reason for hiding this comment

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

yeah that makes sense ofc. i will look into this

.replace("''", "'") // collapse doubled single quotes
}
52 changes: 48 additions & 4 deletions pyrefly/lib/state/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ use ruff_python_ast::Expr;
use ruff_python_ast::ExprAttribute;
use ruff_python_ast::ExprCall;
use ruff_python_ast::ExprContext;
use ruff_python_parser::parse_module;
use ruff_python_parser::TokenKind;
use ruff_python_parser::ParseErrorType;
use ruff_python_parser::LexicalErrorType;
use ruff_python_ast::ExprName;
use ruff_python_ast::Identifier;
use ruff_python_ast::Keyword;
Expand Down Expand Up @@ -72,6 +76,7 @@ use crate::state::require::Require;
use crate::state::state::CancellableTransaction;
use crate::state::state::Transaction;
use crate::types::callable::Param;
use crate::types::literal::Lit;
use crate::types::module::ModuleType;
use crate::types::types::Type;

Expand Down Expand Up @@ -2354,31 +2359,70 @@ impl<'a> Transaction<'a> {
position: TextSize,
completions: &mut Vec<CompletionItem>,
) {
let mut in_string = false;
if let Some(module_info) = self.get_module_info(handle) {
let source = module_info.contents();
match parse_module(source) {
Ok(parsed) => {
for token in parsed.tokens() {
let range = token.range();
if range.contains(position) || (range.end() == position && token.kind() == TokenKind::String) {
if token.kind() == TokenKind::String {
in_string = true;
}
break;
}
}
}
Err(e) => {
if let ParseErrorType::Lexical(LexicalErrorType::UnclosedStringError) = e.error {
if e.location.start() < position {
in_string = true;
}
}
}
}
}

if let Some((callables, chosen_overload_index, active_argument)) =
self.get_callables_from_call(handle, position)
&& let Some(callable) = callables.get(chosen_overload_index)
&& let Some(params) = normalize_singleton_function_type_into_params(callable.clone())
&& let Some(arg_index) = Self::active_parameter_index(&params, &active_argument)
&& let Some(param) = params.get(arg_index)
{
Self::add_literal_completions_from_type(param.as_type(), completions);
Self::add_literal_completions_from_type(param.as_type(), completions, in_string);
}
}

fn add_literal_completions_from_type(param_type: &Type, completions: &mut Vec<CompletionItem>) {
fn add_literal_completions_from_type(
param_type: &Type,
completions: &mut Vec<CompletionItem>,
in_string: bool,
) {
match param_type {
Type::Literal(lit) => {
let label = lit.to_string_escaped(true);
let insert_text = if in_string {
match lit {
Lit::Str(s) => Some(s.to_string()),
_ => None,
}
} else {
None
};
completions.push(CompletionItem {
// TODO: Pass the flag correctly for whether literal string is single quoted or double quoted
label: lit.to_string_escaped(true),
label,
insert_text,
kind: Some(CompletionItemKind::VALUE),
detail: Some(format!("{param_type}")),
..Default::default()
});
}
Type::Union(types) => {
for union_type in types {
Self::add_literal_completions_from_type(union_type, completions);
Self::add_literal_completions_from_type(union_type, completions, in_string);
}
}
_ => {}
Expand Down
104 changes: 104 additions & 0 deletions pyrefly/lib/test/lsp/completion_quotes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@

use lsp_types::CompletionItem;
use lsp_types::CompletionItemKind;
use pretty_assertions::assert_eq;
use pyrefly_build::handle::Handle;
use ruff_text_size::TextSize;

use crate::state::lsp::ImportFormat;
use crate::state::require::Require;
use crate::state::state::State;
use crate::test::util::get_batched_lsp_operations_report_allow_error;

#[derive(Default)]
struct ResultsFilter {
include_keywords: bool,
include_builtins: bool,
}

fn get_default_test_report() -> impl Fn(&State, &Handle, TextSize) -> String {
get_test_report(ResultsFilter::default(), ImportFormat::Absolute)
}

fn get_test_report(
filter: ResultsFilter,
import_format: ImportFormat,
) -> impl Fn(&State, &Handle, TextSize) -> String {
move |state: &State, handle: &Handle, position: TextSize| {
let mut report = "Completion Results:".to_owned();
for CompletionItem {
label,
detail,
kind,
insert_text,
data,
tags,
text_edit,
documentation,
..
} in state
.transaction()
.completion(handle, position, import_format, true)
{
let is_deprecated = if let Some(tags) = tags {
tags.contains(&lsp_types::CompletionItemTag::DEPRECATED)
} else {
false
};
if (filter.include_keywords || kind != Some(CompletionItemKind::KEYWORD))
&& (filter.include_builtins || data != Some(serde_json::json!("builtin")))
{
report.push_str("\n- (");
report.push_str(&format!("{:?}", kind.unwrap()));
report.push_str(") ");
if is_deprecated {
report.push_str("[DEPRECATED] ");
}
report.push_str(&label);
if let Some(detail) = detail {
report.push_str(": ");
report.push_str(&detail);
}
if let Some(insert_text) = insert_text {
report.push_str(" inserting `");
report.push_str(&insert_text);
report.push('`');
}
if let Some(text_edit) = text_edit {
report.push_str(" with text edit: ");
report.push_str(&format!("{:?}", &text_edit));
}
if let Some(documentation) = documentation {
report.push('\n');
match documentation {
lsp_types::Documentation::String(s) => {
report.push_str(&s);
}
lsp_types::Documentation::MarkupContent(content) => {
report.push_str(&content.value);
}
}
}
}
}
report
}
}

#[test]
fn completion_literal_quote_test() {
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of making this new test file, is there a way to include this test in the normal lsp/completion.rs test?

for example, completion_literal_do_not_duplicate_quotes already exists. I notice that test result didn't change. is there a way to update that test instead?

Copy link
Author

Choose a reason for hiding this comment

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

should i write a new test in the completion.rs or i can just rewrite the completion_literal_do_not_duplicate_quotes test?

Copy link
Contributor

Choose a reason for hiding this comment

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

if rewriting completion_literal_do_not_duplicate_quotes is easier, go for it!

Copy link
Author

Choose a reason for hiding this comment

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

okay great i will work on this rn

let code = r#"
from typing import Literal
def foo(fruit: Literal["apple", "pear"]) -> None: ...
foo('
# ^
"#;
let report =
get_batched_lsp_operations_report_allow_error(&[("main", code)], get_default_test_report());

// We expect the completion to NOT insert extra quotes if we are already in a quote.
// Currently it likely inserts quotes.
println!("{}", report);
assert!(report.contains("inserting `apple`"), "Should insert unquoted apple");
assert!(report.contains("inserting `pear`"), "Should insert unquoted pear");
}
1 change: 1 addition & 0 deletions pyrefly/lib/test/lsp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

mod code_actions;
mod completion;
mod completion_quotes;
mod declaration;
mod definition;
mod diagnostic;
Expand Down