diff --git a/pyrefly/lib/lsp/wasm/completion.rs b/pyrefly/lib/lsp/wasm/completion.rs index f20df84290..e4056c3f7d 100644 --- a/pyrefly/lib/lsp/wasm/completion.rs +++ b/pyrefly/lib/lsp/wasm/completion.rs @@ -599,6 +599,12 @@ impl Transaction<'_> { let in_string_literal = nodes .iter() .any(|node| matches!(node, AnyNodeRef::ExprStringLiteral(_))); + self.add_match_literal_completions( + handle, + &nodes, + &mut result, + in_string_literal, + ); self.add_literal_completions(handle, position, &mut result, in_string_literal); self.add_dict_key_completions( handle, diff --git a/pyrefly/lib/state/lsp.rs b/pyrefly/lib/state/lsp.rs index 5810fed89f..75ec0a4a4c 100644 --- a/pyrefly/lib/state/lsp.rs +++ b/pyrefly/lib/state/lsp.rs @@ -2584,6 +2584,47 @@ impl<'a> Transaction<'a> { Some(references) } + /// Suggest Literal values when completing inside a `match` value pattern. + /// + /// We can't reuse the call-argument literal completion path here because + /// `case :` isn't a call site, so we never get parameter types to + /// infer literals from. Instead, we look for a match value/singleton + /// pattern at the cursor and pull the `match` subject's type to surface + /// its Literal members. + pub(crate) fn add_match_literal_completions( + &self, + handle: &Handle, + covering_nodes: &[AnyNodeRef], + completions: &mut Vec, + in_string_literal: bool, + ) { + let mut is_match_value_pattern = false; + let mut subject = None; + for node in covering_nodes { + match node { + AnyNodeRef::PatternMatchValue(_) | AnyNodeRef::PatternMatchSingleton(_) => { + is_match_value_pattern = true; + } + AnyNodeRef::StmtMatch(stmt_match) => { + subject = Some(stmt_match.subject.as_ref()); + } + _ => {} + } + if is_match_value_pattern && subject.is_some() { + break; + } + } + if !is_match_value_pattern { + return; + } + let Some(subject) = subject else { + return; + }; + if let Some(subject_type) = self.get_type_trace(handle, subject.range()) { + Self::add_literal_completions_from_type(&subject_type, completions, in_string_literal); + } + } + // Kept for backwards compatibility - used by external callers (lsp/server.rs, playground.rs) // who don't need the is_incomplete flag pub fn completion( diff --git a/pyrefly/lib/test/lsp/completion.rs b/pyrefly/lib/test/lsp/completion.rs index 51e5324fd7..35946ee00c 100644 --- a/pyrefly/lib/test/lsp/completion.rs +++ b/pyrefly/lib/test/lsp/completion.rs @@ -1386,6 +1386,31 @@ Completion Results: ); } +#[test] +fn completion_literal_match_value() { + let code = r#" +from typing import Literal +x: Literal['a', 'b'] = 'a' +match x: + case ': +# ^ +"#; + let report = + get_batched_lsp_operations_report_allow_error(&[("main", code)], get_default_test_report()); + assert_eq!( + r#" +# main.py +5 | case ': + ^ +Completion Results: +- (Value) 'a': Literal['a'] inserting `a` +- (Value) 'b': Literal['b'] inserting `b` +"# + .trim(), + report.trim(), + ); +} + #[test] fn completion_literal_union_alias() { let code = r#"