feat: complete array/tuple literals (#201)

* dev: introduce type checking on arrays/tuples

* dev: complete array literals

* dev: complete columns/ros/gutter/column-gutter/row-gutter/size/dash array types

* chore: reduce two todos
This commit is contained in:
Myriad-Dreamin 2024-04-20 11:47:53 +08:00 committed by GitHub
parent f6f2454d37
commit fa0899b7cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 332 additions and 175 deletions

View file

@ -295,7 +295,17 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
fn check_array(&mut self, root: LinkedNode<'_>) -> Option<FlowType> {
let _arr: ast::Array = root.cast()?;
Some(FlowType::Array)
let mut elements = EcoVec::new();
for elem in root.children() {
let ty = self.check(elem);
if matches!(ty, FlowType::Clause) {
continue;
}
elements.push(ty);
}
Some(FlowType::Tuple(elements))
}
fn check_dict(&mut self, root: LinkedNode<'_>) -> Option<FlowType> {
@ -700,6 +710,8 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
candidates.push(f.ret.clone());
}
FlowType::Dict(_v) => {}
FlowType::Tuple(_v) => {}
FlowType::Array(_v) => {}
// todo: with
FlowType::With(_e) => {}
FlowType::Args(_e) => {}
@ -716,7 +728,6 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
}
}
FlowType::Array => {}
FlowType::Clause => {}
FlowType::Undef => {}
FlowType::Content => {}
@ -902,7 +913,8 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
FlowType::Value(..) => e,
FlowType::ValueDoc(..) => e,
FlowType::Array => e,
FlowType::Tuple(..) => e,
FlowType::Array(..) => e,
FlowType::Clause => e,
FlowType::Undef => e,
FlowType::Content => e,
@ -957,7 +969,8 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
}
_ => {}
},
FlowType::Array => {}
FlowType::Array(..) => {}
FlowType::Dict(..) => {}
_ => {}
}
@ -1104,6 +1117,14 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
self.analyze(p, pol);
}
}
FlowType::Tuple(e) => {
for ty in e.iter() {
self.analyze(ty, pol);
}
}
FlowType::Array(e) => {
self.analyze(e, pol);
}
FlowType::With(w) => {
self.analyze(&w.0, pol);
for m in &w.1 {
@ -1155,8 +1176,6 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
FlowType::FlowNone => {}
FlowType::Auto => {}
FlowType::Builtin(_) => {}
// todo
FlowType::Array => {}
FlowType::Element(_) => {}
}
}
@ -1230,6 +1249,16 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
FlowType::Dict(FlowRecord { fields })
}
FlowType::Tuple(e) => {
let e2 = e.iter().map(|ty| self.transform(ty, pol)).collect();
FlowType::Tuple(e2)
}
FlowType::Array(e) => {
let e2 = self.transform(e, pol);
FlowType::Array(Box::new(e2))
}
FlowType::With(w) => {
let primary = self.transform(&w.0, pol);
FlowType::With(Box::new((primary, w.1.clone())))
@ -1274,7 +1303,6 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
}
// todo
FlowType::Let(_) => FlowType::Any,
FlowType::Array => FlowType::Array,
FlowType::Value(v) => FlowType::Value(v.clone()),
FlowType::ValueDoc(v) => FlowType::ValueDoc(v.clone()),
FlowType::Element(v) => FlowType::Element(*v),
@ -1337,8 +1365,10 @@ impl Joiner {
(FlowType::Content, _) => self.definite = FlowType::Undef,
(FlowType::Var(v), _) => self.possibles.push(FlowType::Var(v)),
// todo: check possibles
(FlowType::Array, FlowType::None) => self.definite = FlowType::Array,
(FlowType::Array, _) => self.definite = FlowType::Undef,
(FlowType::Array(e), FlowType::None) => self.definite = FlowType::Array(e),
(FlowType::Array(..), _) => self.definite = FlowType::Undef,
(FlowType::Tuple(e), FlowType::None) => self.definite = FlowType::Tuple(e),
(FlowType::Tuple(..), _) => self.definite = FlowType::Undef,
// todo: possible some style
(FlowType::Auto, FlowType::None) => self.definite = FlowType::Auto,
(FlowType::Auto, _) => self.definite = FlowType::Undef,

View file

@ -2,7 +2,7 @@ use ecow::EcoVec;
use once_cell::sync::Lazy;
use regex::RegexSet;
use typst::{
foundations::{Func, ParamInfo, Value},
foundations::{Func, ParamInfo, Type, Value},
syntax::Span,
};
@ -81,75 +81,6 @@ impl PathPreference {
}
}
pub(in crate::analysis::ty) fn param_mapping(f: &Func, p: &ParamInfo) -> Option<FlowType> {
match (f.name().unwrap(), p.name) {
("cbor", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::None,
))),
("csv", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::Csv,
))),
("image", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::Image,
))),
("read", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::None,
))),
("json", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::Json,
))),
("yaml", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::Yaml,
))),
("xml", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::Xml,
))),
("toml", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::Toml,
))),
("raw", "theme") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::RawTheme,
))),
("raw", "syntaxes") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::RawSyntax,
))),
("bibliography", "path") => Some(FlowType::Builtin(FlowBuiltinType::Path(
PathPreference::Bibliography,
))),
("text", "size") => Some(FlowType::Builtin(FlowBuiltinType::TextSize)),
("text", "font") => Some(FlowType::Builtin(FlowBuiltinType::TextFont)),
("text", "lang") => Some(FlowType::Builtin(FlowBuiltinType::TextLang)),
("text", "region") => Some(FlowType::Builtin(FlowBuiltinType::TextRegion)),
("text" | "stack", "dir") => Some(FlowType::Builtin(FlowBuiltinType::Dir)),
(
// todo: polygon.regular
"page" | "highlight" | "text" | "path" | "rect" | "ellipse" | "circle" | "polygon"
| "box" | "block" | "table" | "regular",
"fill",
) => Some(FlowType::Builtin(FlowBuiltinType::Color)),
(
// todo: table.cell
"table" | "cell" | "block" | "box" | "circle" | "ellipse" | "rect" | "square",
"inset",
) => Some(FlowType::Builtin(FlowBuiltinType::Inset)),
("block" | "box" | "circle" | "ellipse" | "rect" | "square", "outset") => {
Some(FlowType::Builtin(FlowBuiltinType::Outset))
}
("block" | "box" | "rect" | "square", "radius") => {
Some(FlowType::Builtin(FlowBuiltinType::Radius))
}
(
//todo: table.cell, table.hline, table.vline, math.cancel, grid.cell, polygon.regular
"cancel" | "highlight" | "overline" | "strike" | "underline" | "text" | "path" | "rect"
| "ellipse" | "circle" | "polygon" | "box" | "block" | "table" | "line" | "cell"
| "hline" | "vline" | "regular",
"stroke",
) => Some(FlowType::Builtin(FlowBuiltinType::Stroke)),
("page", "margin") => Some(FlowType::Builtin(FlowBuiltinType::Margin)),
_ => None,
}
}
#[derive(Debug, Clone, Hash)]
pub(crate) enum FlowBuiltinType {
Args,
@ -172,26 +103,34 @@ pub(crate) enum FlowBuiltinType {
Path(PathPreference),
}
use FlowBuiltinType::*;
fn literally(s: impl FlowBuiltinLiterally) -> FlowType {
s.literally()
}
trait FlowBuiltinLiterally {
fn literally(&self) -> FlowType;
fn literally(self) -> FlowType;
}
impl FlowBuiltinLiterally for &str {
fn literally(&self) -> FlowType {
fn literally(self) -> FlowType {
FlowType::Value(Box::new((Value::Str((*self).into()), Span::detached())))
}
}
impl FlowBuiltinLiterally for FlowBuiltinType {
fn literally(&self) -> FlowType {
fn literally(self) -> FlowType {
FlowType::Builtin(self.clone())
}
}
impl FlowBuiltinLiterally for FlowType {
fn literally(self) -> FlowType {
self
}
}
// separate by middle
macro_rules! flow_builtin_union_inner {
($literal_kind:expr) => {
@ -228,7 +167,99 @@ macro_rules! flow_record {
};
}
use FlowBuiltinType::*;
pub(in crate::analysis::ty) fn param_mapping(f: &Func, p: &ParamInfo) -> Option<FlowType> {
match (f.name().unwrap(), p.name) {
("cbor", "path") => Some(literally(Path(PathPreference::None))),
("csv", "path") => Some(literally(Path(PathPreference::Csv))),
("image", "path") => Some(literally(Path(PathPreference::Image))),
("read", "path") => Some(literally(Path(PathPreference::None))),
("json", "path") => Some(literally(Path(PathPreference::Json))),
("yaml", "path") => Some(literally(Path(PathPreference::Yaml))),
("xml", "path") => Some(literally(Path(PathPreference::Xml))),
("toml", "path") => Some(literally(Path(PathPreference::Toml))),
("raw", "theme") => Some(literally(Path(PathPreference::RawTheme))),
("raw", "syntaxes") => Some(literally(Path(PathPreference::RawSyntax))),
("bibliography", "path") => Some(literally(Path(PathPreference::Bibliography))),
("text", "size") => Some(literally(TextSize)),
("text", "font") => {
static FONT_TYPE: Lazy<FlowType> = Lazy::new(|| {
FlowType::Union(Box::new(vec![
literally(TextFont),
FlowType::Array(Box::new(literally(TextFont))),
]))
});
Some(FONT_TYPE.clone())
}
("text", "lang") => Some(literally(TextLang)),
("text", "region") => Some(literally(TextRegion)),
("text" | "stack", "dir") => Some(literally(Dir)),
(
// todo: polygon.regular
"page" | "highlight" | "text" | "path" | "rect" | "ellipse" | "circle" | "polygon"
| "box" | "block" | "table" | "regular",
"fill",
) => Some(literally(Color)),
(
// todo: table.cell
"table" | "cell" | "block" | "box" | "circle" | "ellipse" | "rect" | "square",
"inset",
) => Some(literally(Inset)),
("block" | "box" | "circle" | "ellipse" | "rect" | "square", "outset") => {
Some(literally(Outset))
}
("block" | "box" | "rect" | "square", "radius") => Some(literally(Radius)),
("grid" | "table", "columns" | "rows" | "gutter" | "column-gutter" | "row-gutter") => {
static COLUMN_TYPE: Lazy<FlowType> = Lazy::new(|| {
flow_union!(
FlowType::Value(Box::new((Value::Auto, Span::detached()))),
FlowType::Value(Box::new((Value::Type(Type::of::<i64>()), Span::detached()))),
literally(Length),
FlowType::Array(Box::new(literally(Length))),
)
});
Some(COLUMN_TYPE.clone())
}
("pattern", "size") => {
static PATTERN_SIZE_TYPE: Lazy<FlowType> = Lazy::new(|| {
flow_union!(
FlowType::Value(Box::new((Value::Auto, Span::detached()))),
FlowType::Array(Box::new(FlowType::Builtin(Length))),
)
});
Some(PATTERN_SIZE_TYPE.clone())
}
("stroke", "dash") => Some(FLOW_STROKE_DASH_TYPE.clone()),
(
//todo: table.cell, table.hline, table.vline, math.cancel, grid.cell, polygon.regular
"cancel" | "highlight" | "overline" | "strike" | "underline" | "text" | "path" | "rect"
| "ellipse" | "circle" | "polygon" | "box" | "block" | "table" | "line" | "cell"
| "hline" | "vline" | "regular",
"stroke",
) => Some(FlowType::Builtin(Stroke)),
("page", "margin") => Some(FlowType::Builtin(Margin)),
_ => None,
}
}
static FLOW_STROKE_DASH_TYPE: Lazy<FlowType> = Lazy::new(|| {
flow_union!(
"solid",
"dotted",
"densely-dotted",
"loosely-dotted",
"dashed",
"densely-dashed",
"loosely-dashed",
"dash-dotted",
"densely-dash-dotted",
"loosely-dash-dotted",
FlowType::Array(Box::new(flow_union!("dot", literally(Float)))),
FlowType::Dict(flow_record!(
"array" => FlowType::Array(Box::new(flow_union!("dot", literally(Float)))),
"phase" => literally(Length),
))
)
});
pub static FLOW_STROKE_DICT: Lazy<FlowRecord> = Lazy::new(|| {
flow_record!(
@ -236,18 +267,7 @@ pub static FLOW_STROKE_DICT: Lazy<FlowRecord> = Lazy::new(|| {
"thickness" => literally(Length),
"cap" => flow_union!("butt", "round", "square"),
"join" => flow_union!("miter", "round", "bevel"),
"dash" => flow_union!(
"solid",
"dotted",
"densely-dotted",
"loosely-dotted",
"dashed",
"densely-dashed",
"loosely-dashed",
"dash-dotted",
"densely-dash-dotted",
"loosely-dash-dotted",
),
"dash" => FLOW_STROKE_DASH_TYPE.clone(),
"miter-limit" => literally(Float),
)
});
@ -318,10 +338,6 @@ pub static FLOW_RADIUS_DICT: Lazy<FlowRecord> = Lazy::new(|| {
// todo: math.cancel.angle can be a function
// todo: text.features array/dictionary
// todo: math.mat.augment
// todo: text.lang
// todo: text.region
// todo: text.font array
// todo: stroke.dash can be an array
// todo: csv.row-type can be an array or a dictionary
// ISO 639

View file

@ -31,7 +31,6 @@ pub(crate) enum FlowType {
Undef,
Content,
Any,
Array,
None,
Infer,
FlowNone,
@ -44,6 +43,9 @@ pub(crate) enum FlowType {
Var(Box<(DefId, EcoString)>),
Func(Box<FlowSignature>),
Dict(FlowRecord),
Array(Box<FlowType>),
// Note: may contains spread types
Tuple(EcoVec<FlowType>),
With(Box<(FlowType, Vec<FlowArgs>)>),
Args(Box<FlowArgs>),
At(FlowAt),
@ -61,7 +63,6 @@ impl fmt::Debug for FlowType {
FlowType::Undef => f.write_str("Undef"),
FlowType::Content => f.write_str("Content"),
FlowType::Any => f.write_str("Any"),
FlowType::Array => f.write_str("Array"),
FlowType::None => f.write_str("None"),
FlowType::Infer => f.write_str("Infer"),
FlowType::FlowNone => f.write_str("FlowNone"),
@ -70,6 +71,14 @@ impl fmt::Debug for FlowType {
FlowType::Args(a) => write!(f, "&({a:?})"),
FlowType::Func(s) => write!(f, "{s:?}"),
FlowType::Dict(r) => write!(f, "{r:?}"),
FlowType::Array(a) => write!(f, "Array<{a:?}>"),
FlowType::Tuple(t) => {
f.write_str("(")?;
for t in t {
write!(f, "{t:?}, ")?;
}
f.write_str(")")
}
FlowType::With(w) => write!(f, "({:?}).with(..{:?})", w.0, w.1),
FlowType::At(a) => write!(f, "{a:?}"),
FlowType::Union(u) => {

View file

@ -7,8 +7,8 @@ input_file: crates/tinymist-query/src/fixtures/type_check/infer2.typ
1..52 -> Element(text)
6..15 -> TextSize
12..15 -> TextSize
17..25 -> TextFont
23..25 -> TextFont
17..25 -> (TextFont | Array<TextFont>)
23..25 -> (TextFont | Array<TextFont>)
27..38 -> Stroke
35..38 -> Stroke
40..49 -> Color

View file

@ -0,0 +1,30 @@
---
source: crates/tinymist-query/src/analysis.rs
expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/text_font.typ
---
"x" = "Test"
"y" = ("Test", )
---
1..21 -> Element(text)
6..18 -> (TextFont | Array<TextFont>)
12..18 -> (TextFont | Array<TextFont>)
19..21 -> Type(content)
23..39 -> Element(text)
28..36 -> (TextFont | Array<TextFont>)
34..36 -> (TextFont | Array<TextFont>)
37..39 -> Type(content)
41..64 -> Element(text)
46..61 -> (TextFont | Array<TextFont>)
52..61 -> (TextFont | Array<TextFont>)
62..64 -> Type(content)
70..71 -> @x
82..97 -> Element(text)
87..94 -> (TextFont | Array<TextFont>)
93..94 -> (TextFont | Array<TextFont>)
95..97 -> Type(content)
103..104 -> @y
118..133 -> Element(text)
123..130 -> (TextFont | Array<TextFont>)
129..130 -> (TextFont | Array<TextFont>)
131..133 -> Type(content)

View file

@ -0,0 +1,7 @@
#text(font: "Test")[]
#text(font: ())[]
#text(font: ("Test",))[]
#let x = "Test"
#text(font: x)[]
#let y = ("Test",)
#text(font: y)[]

View file

@ -2,16 +2,17 @@ use std::collections::{BTreeMap, HashSet};
use ecow::{eco_format, EcoString};
use lsp_types::{CompletionItem, CompletionTextEdit, InsertTextFormat, TextEdit};
use once_cell::sync::OnceCell;
use reflexo::path::{unix_slash, PathClean};
use typst::foundations::{AutoValue, Func, Label, NoneValue, Type, Value};
use typst::layout::Length;
use typst::layout::{Dir, Length};
use typst::syntax::ast::AstNode;
use typst::syntax::{ast, Span, SyntaxKind};
use typst::visualize::Color;
use super::{Completion, CompletionContext, CompletionKind};
use crate::analysis::{
analyze_dyn_signature, analyze_import, resolve_callee, FlowBuiltinType, FlowType,
analyze_dyn_signature, analyze_import, resolve_callee, FlowBuiltinType, FlowRecord, FlowType,
PathPreference, FLOW_INSET_DICT, FLOW_MARGIN_DICT, FLOW_OUTSET_DICT, FLOW_RADIUS_DICT,
FLOW_STROKE_DICT,
};
@ -326,7 +327,7 @@ fn type_completion(
FlowType::Undef => return None,
FlowType::Content => return None,
FlowType::Any => return None,
FlowType::Array => {
FlowType::Tuple(..) | FlowType::Array(..) => {
ctx.snippet_completion("()", "(${})", "An array.");
}
FlowType::Dict(..) => {
@ -356,8 +357,8 @@ fn type_completion(
FlowBuiltinType::Stroke => {
ctx.snippet_completion("stroke()", "stroke(${})", "Stroke type.");
ctx.snippet_completion("()", "(${})", "Stroke dictionary.");
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Color)), None);
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), None);
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Color)), docs);
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs);
}
FlowBuiltinType::Color => {
ctx.snippet_completion("luma()", "luma(${v})", "A custom grayscale color.");
@ -426,23 +427,28 @@ fn type_completion(
});
}
}
FlowBuiltinType::TextFont => return None,
FlowBuiltinType::Dir => return None,
FlowBuiltinType::Dir => {
let ty = Type::of::<Dir>();
ctx.strict_scope_completions(false, |value| value.ty() == ty);
}
FlowBuiltinType::TextFont => {
ctx.font_completions();
}
FlowBuiltinType::Margin => {
ctx.snippet_completion("()", "(${})", "Margin dictionary.");
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), None);
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs);
}
FlowBuiltinType::Inset => {
ctx.snippet_completion("()", "(${})", "Inset dictionary.");
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), None);
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs);
}
FlowBuiltinType::Outset => {
ctx.snippet_completion("()", "(${})", "Outset dictionary.");
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), None);
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs);
}
FlowBuiltinType::Radius => {
ctx.snippet_completion("()", "(${})", "Radius dictionary.");
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), None);
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs);
}
FlowBuiltinType::Length => {
ctx.snippet_completion("pt", "${1}pt", "Point length unit.");
@ -452,6 +458,7 @@ fn type_completion(
ctx.snippet_completion("em", "${1}em", "Em length unit.");
let length_ty = Type::of::<Length>();
ctx.strict_scope_completions(false, |value| value.ty() == length_ty);
type_completion(ctx, Some(&FlowType::Auto), docs);
}
FlowBuiltinType::Float => {
ctx.snippet_completion("exponential notation", "${1}e${0}", "Exponential notation");
@ -474,9 +481,9 @@ fn type_completion(
FlowType::Value(v) => {
if let Value::Type(ty) = &v.0 {
if *ty == Type::of::<NoneValue>() {
ctx.snippet_completion("none", "none", "Nothing.")
type_completion(ctx, Some(&FlowType::None), docs);
} else if *ty == Type::of::<AutoValue>() {
ctx.snippet_completion("auto", "auto", "A smart default.");
type_completion(ctx, Some(&FlowType::Auto), docs);
} else if *ty == Type::of::<bool>() {
ctx.snippet_completion("false", "false", "No / Disabled.");
ctx.snippet_completion("true", "true", "Yes / Enabled.");
@ -501,13 +508,24 @@ fn type_completion(
});
ctx.strict_scope_completions(false, |value| value.ty() == *ty);
}
} else if v.0.ty() == Type::of::<NoneValue>() {
type_completion(ctx, Some(&FlowType::None), docs);
} else if v.0.ty() == Type::of::<AutoValue>() {
type_completion(ctx, Some(&FlowType::Auto), docs);
} else {
ctx.value_completion(None, &v.0, true, docs);
}
}
FlowType::ValueDoc(v) => {
let (value, docs) = v.as_ref();
ctx.value_completion(None, value, true, Some(docs));
type_completion(
ctx,
Some(&FlowType::Value(Box::new((
value.clone(),
Span::detached(),
)))),
Some(*docs),
);
}
FlowType::Element(e) => {
ctx.value_completion(Some(e.name().into()), &Value::Func((*e).into()), true, docs);
@ -581,9 +599,6 @@ pub fn named_param_value_completions<'a>(
{
ctx.cast_completions(&param.input);
}
if name == "font" {
ctx.font_completions();
}
if ctx.before.ends_with(':') {
ctx.enrich(" ", "");
@ -612,21 +627,18 @@ pub fn complete_literal(ctx: &mut CompletionContext) -> Option<()> {
log::debug!("check complete_literal 3: {:?}", ctx.leaf);
// or empty array
let dict_span;
let dict_lit = match parent.kind() {
let lit_span;
let (dict_lit, _tuple_lit) = match parent.kind() {
SyntaxKind::Dict => {
let dict_lit = parent.get().cast::<ast::Dict>()?;
dict_span = dict_lit.span();
dict_lit
lit_span = dict_lit.span();
(dict_lit, None)
}
SyntaxKind::Array => {
let w = parent.get().cast::<ast::Array>()?;
if w.items().next().is_some() {
return None;
}
dict_span = w.span();
ast::Dict::default()
lit_span = w.span();
(ast::Dict::default(), Some(w))
}
_ => return None,
};
@ -634,61 +646,114 @@ pub fn complete_literal(ctx: &mut CompletionContext) -> Option<()> {
// query type of the dict
let named_span = named.map(|n| n.span()).unwrap_or_else(Span::detached);
let named_ty = ctx.ctx.type_of_span(named_span);
let dict_ty = ctx.ctx.type_of_span(dict_span);
log::info!("complete_literal: {:?} {:?}", dict_ty, named_ty);
let lit_ty = ctx.ctx.type_of_span(lit_span);
log::info!("complete_literal: {lit_ty:?} {named_ty:?}");
// todo: check if the dict is named
if named_ty.is_some() {
let res = type_completion(ctx, named_ty.as_ref(), None);
if res.is_some() {
ctx.incomplete = false;
}
return res;
enum LitComplAction<'a> {
Dict(&'a FlowRecord),
Positional(&'a FlowType),
}
let existing = OnceCell::new();
struct LitComplWorker<'a, 'b, 'w> {
ctx: &'a mut CompletionContext<'b, 'w>,
dict_lit: ast::Dict<'a>,
existing: &'a OnceCell<HashSet<EcoString>>,
}
let existing = dict_lit
.items()
.filter_map(|field| match field {
ast::DictItem::Named(n) => Some(n.name().get().clone()),
ast::DictItem::Keyed(k) => {
let key = ctx.ctx.const_eval(k.key());
if let Some(Value::Str(key)) = key {
return Some(key.into());
}
None
}
// todo: var dict union
ast::DictItem::Spread(_s) => None,
})
.collect::<HashSet<_>>();
let dict_ty = dict_ty?;
let dict_interface = match dict_ty {
FlowType::Builtin(FlowBuiltinType::Stroke) => &FLOW_STROKE_DICT,
FlowType::Builtin(FlowBuiltinType::Margin) => &FLOW_MARGIN_DICT,
FlowType::Builtin(FlowBuiltinType::Inset) => &FLOW_INSET_DICT,
FlowType::Builtin(FlowBuiltinType::Outset) => &FLOW_OUTSET_DICT,
FlowType::Builtin(FlowBuiltinType::Radius) => &FLOW_RADIUS_DICT,
_ => return None,
let mut ctx = LitComplWorker {
ctx,
dict_lit,
existing: &existing,
};
for (key, _, _) in dict_interface.fields.iter() {
if existing.contains(key) {
continue;
impl<'a, 'b, 'w> LitComplWorker<'a, 'b, 'w> {
fn on_iface(&mut self, lit_interface: LitComplAction<'_>) {
match lit_interface {
LitComplAction::Positional(a) => {
type_completion(self.ctx, Some(a), None);
}
LitComplAction::Dict(dict_iface) => {
let existing = self.existing.get_or_init(|| {
self.dict_lit
.items()
.filter_map(|field| match field {
ast::DictItem::Named(n) => Some(n.name().get().clone()),
ast::DictItem::Keyed(k) => {
let key = self.ctx.ctx.const_eval(k.key());
if let Some(Value::Str(key)) = key {
return Some(key.into());
}
None
}
// todo: var dict union
ast::DictItem::Spread(_s) => None,
})
.collect::<HashSet<_>>()
});
for (key, _, _) in dict_iface.fields.iter() {
if existing.contains(key) {
continue;
}
self.ctx.completions.push(Completion {
kind: CompletionKind::Field,
label: key.clone(),
apply: Some(eco_format!("{}: ${{}}", key)),
detail: None,
label_detail: None,
// todo: only vscode and neovim (0.9.1) support this
command: Some("editor.action.triggerSuggest"),
});
}
}
}
}
ctx.completions.push(Completion {
kind: CompletionKind::Field,
label: key.clone(),
apply: Some(eco_format!("{}: ${{}}", key)),
detail: None,
label_detail: None,
// todo: only vscode and neovim (0.9.1) support this
command: Some("editor.action.triggerSuggest"),
});
fn on_lit_ty(&mut self, ty: &FlowType) {
match ty {
FlowType::Builtin(FlowBuiltinType::Stroke) => {
self.on_iface(LitComplAction::Dict(&FLOW_STROKE_DICT))
}
FlowType::Builtin(FlowBuiltinType::Margin) => {
self.on_iface(LitComplAction::Dict(&FLOW_MARGIN_DICT))
}
FlowType::Builtin(FlowBuiltinType::Inset) => {
self.on_iface(LitComplAction::Dict(&FLOW_INSET_DICT))
}
FlowType::Builtin(FlowBuiltinType::Outset) => {
self.on_iface(LitComplAction::Dict(&FLOW_OUTSET_DICT))
}
FlowType::Builtin(FlowBuiltinType::Radius) => {
self.on_iface(LitComplAction::Dict(&FLOW_RADIUS_DICT))
}
FlowType::Dict(d) => self.on_iface(LitComplAction::Dict(d)),
FlowType::Array(a) => self.on_iface(LitComplAction::Positional(a)),
FlowType::Union(u) => {
for info in u.as_ref() {
self.on_lit_ty(info);
}
}
// todo: var, let, etc.
_ => {}
}
}
fn work(&mut self, named_ty: Option<FlowType>, lit_ty: Option<FlowType>) {
if let Some(named_ty) = &named_ty {
type_completion(self.ctx, Some(named_ty), None);
} else if let Some(lit_ty) = &lit_ty {
self.on_lit_ty(lit_ty);
}
}
}
ctx.work(named_ty, lit_ty);
let ctx = ctx.ctx;
if ctx.before.ends_with(',') {
ctx.enrich(" ", "");
}