FTS code analyzer index_operators: greedy matching turns nested generics into >> and a<-1 into <-
Maintainers usually reply within 3 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 38/100
Research direction
Start with src/lance/rust/lance-tokenizer/src/code_tokenizer.rs, especially is_operator_char, MULTI_CHAR_OPERATORS, and operator_token_end. Run test_code_lex_tokenizer_splits_adjacent_operators and the provided hunt/tokenization/r5.py reproduction to compare nested generic, comparison, and dot-operator tokenization. Done requires an agreed tokenization behavior, regression coverage, and documentation for any compatibility impact.
Written by the indexing model from the issue text.
Description
Bug bash: Needs team input — design question
Bug bash priority: Medium · critical-fix: no
From the FTS for Code bug bash (2026-10-01), findings: tokenization-5.md. Repros ran against released artifacts (pylance 12.0.0 / 13.0.0b18, lancedb 0.39.0 / 0.40.0b12, @lancedb/lancedb 0.40.0-beta.12) unless stated. $S is the bug bash scratch dir.
Decision needed: which operators are atomic tokens.
Options:
- Index single-character angle brackets and comparison operators individually.
- Keep longest-match but special-case
>>/<<inside generics. This is not lexically decidable without language awareness. - Document the current behavior.
Recommendation: 1. It also affects existing indexes (see the tokenizer-compatibility question on the underscore-identifier issue).
- Category: bug
- Environment: pylance local, Python 3.12, Linux aarch64
- Artifacts: pylance 13.0.0b18 and 12.0.0 (identical output)
- Target tracker: GitHub
lance-format/lance
Repro
$S/hunt/tokenization/r5.py:
import lance, pyarrow as pa, tempfile
from lance.query import MatchQuery, PhraseQuery
print("pylance", lance.__version__)
O = dict(analyzer="code", index_operators=True)
for s in ["Vec<Vec<u8>>", "HashMap<String, Vec<i32>>", "a<-1", "x <- 1", "opt?.value", "a..b", "a...b"]:
print(f"{s!r:30}", [t.text for t in lance.tokenize(s, **O)])
docs = ["let v: Vec<Vec<u8>> = vec![];", "let v: Vec<u8> = vec![];", "map: HashMap<String, Vec<i32>>", "x <- 1", "if a<-1 { }"]
ds = lance.write_dataset(pa.table({"id": list(range(5)), "code": docs}), tempfile.mkdtemp())
ds.create_scalar_index("code", "INVERTED", with_position=True, **O)
r = lambda q: ds.to_table(full_text_query=q, columns=["id", "_score"])["id"].to_pylist()
print('phrase "Vec<u8>" ->', r(PhraseQuery("Vec<u8>", "code")))
print('phrase "Vec<i32>" ->', r(PhraseQuery("Vec<i32>", "code")))
print('match ">" ->', r(MatchQuery(">", "code")))
print('match "<-" ->', r(MatchQuery("<-", "code")))
Output:
pylance 13.0.0-beta.18
'Vec<Vec<u8>>' ['vec', '<', 'vec', '<', 'u8', '>>']
'HashMap<String, Vec<i32>>' ['hashmap', '<', 'string', 'vec', '<', 'i32', '>>']
'a<-1' ['a', '<-', '1']
'x <- 1' ['x', '<-', '1']
'opt?.value' ['opt', '?', 'value']
'a..b' ['a', 'b']
'a...b' ['a', 'b']
phrase "Vec<u8>" -> [1]
phrase "Vec<i32>" -> []
match ">" -> [1]
match "<-" -> [3, 4]
Problems, in order of impact:
- Closing nested generics (
Vec<Vec<u8>>,Map<K, List<V>>,Option<Box<T>>) become>>, the shift operator. PhraseVec<i32>misses row 2. Match>misses row 0, the most common place>appears in Rust/Java/C++/TS types. a<-1(less-than negative) is tokenized as the R/Go/Haskell<-operator. A search for<-matches row 4.?.(optional chaining in JS/TS/Kotlin/C#),../..=(Rust ranges),...(spread/varargs), and.are not operator characters.?.indexes as?, and the others are dropped entirely, so they cannot be searched.
Expected behavior
Vec<Vec<u8>> yields >,> (at minimum the closing > tokens remain findable as >). Searching Vec<i32> as a phrase finds HashMap<String, Vec<i32>>. Common multi-character operators that contain . (?., .., ...) are indexable, or the docs say they are not.
Evidence: existing test test_code_lex_tokenizer_splits_adjacent_operators (src/lance/rust/lance-tokenizer/src/code_tokenizer.rs:182-190) expects parse::<usize>() to give <, > as separate tokens. So generic brackets are meant to be single </> tokens, and the nested case simply has no test.
Suspected root cause
operator_token_end (src/lance/rust/lance-tokenizer/src/code_tokenizer.rs:96-113) takes the longest entry of MULTI_CHAR_OPERATORS (:76-80, which includes >>, >>>, >>=, <-) that prefixes the remaining text, with no context. is_operator_char (:69-74) excludes ., so ?., .. and ... cannot form tokens.
Fix direction
Triage. Options: emit >>/>>> also as > alternatives at the same position (like preserved originals), or make the shift-family tokens only bind when not closing a < opened earlier in the stream. Add .-containing operators to the list, or document the exclusions. Any change alters tokenization of existing indexes.
- Dominant language
- Rust
- Stars
- 7.2k
- Forks
- 900
- Avg merge
- 4d 22h
- Merged PRs (30d)
- 366
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- Ships a Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from lance-format/lance
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 78/100
lance-format/lance#10225 ·
Maintainers usually reply within 3 days
-
feature performance
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
lance-format/lance#10137 ·
Maintainers usually reply within 3 days
-
After initialize_mem_wal, every FTS query logs "ERROR Index __lance_mem_wal has no fields"Possibly taken @lance-gatefixer claimed this 23 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
lance-format/lance#9655 ·
Maintainers usually reply within 3 days
-
Upgrade DataFusion for the unparser stack-overflow fixMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
lance-format/lance#8225 ·
Maintainers usually reply within 3 days
-
enhancement
Difficulty 1/5 Under an hour Newbie friendliness 84/100
lance-format/lance#6425 · 2 comments ·
Maintainers usually reply within 3 days
All issues in lance-format/lance
Similar issues
-
bug easy good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
lacs-project/sysknife#659 ·
Maintainers usually reply within 2 days
-
E_FIT_INPUT_MISSING (mixture overrides): the message says ferx-r bundles lack mixture_overridesOpenP3
Difficulty 1/5 Under an hour Newbie friendliness 83/100
Maintainers usually reply within 1 day
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
registrystack/registry-stack#2431 ·
Maintainers usually reply within 1 day
-
contribution
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
farion1231/cc-switch#8863 · 1 comment ·
Maintainers usually reply within 1 day