opentau
App Listed Dormant
Using Large Language Models for Repo-wide Type Prediction
- Downloads
- Not measured
- Dependents
- Not applicable
- Stars
- 121
- Forks
- 7
- Contributors
- 2
- Commits
- 513
About
OpenTau: Using Large Language Models for Gradual Type Inference
Implementation for the paper: Type Prediction With Program Decomposition and Fill-in-the-Type Training. Federico Cassano, Ming-Ho Yee, Noah Shinn, Arjun Guha, Steven Holtzen.
Type inference for gradually-typed languages such as TypeScript and Python has become increasingly prevalent in the field of programming languages. However, current approaches often struggle with inferring descriptive types in cases in which user-defined type annotations are absent, especially when inferring function signatures.
Read the full README
This has motivated automated type prediction: given an untyped program, produce a well-typed output program. Large language models (LLMs) are promising for type prediction, but there are challenges: fill-in-the-middle performs poorly, programs may not fit into the context window, generated types may not type check, and it is difficult to measure how well-typed the output program is. We address these challenges by building OpenTau, a search-based approach for type prediction that leverages large language models. We propose a new metric for type prediction quality, give a tree-based program decomposition that searches a space of generated types, and present fill-in-the-type fine-tuning for LLMs. We evaluate our work with a new dataset for TypeScript type prediction, and show that 47.4% of files type check (14.5% absolute improvement) with an overall rate of 3.3 type errors per file.
Additionally, we build two protocols for implementing additional languages and models. In our work, we implement a TypeScript compiler that respects the protocol and a SantaCoder server that respects the other protocol. An optional OpenAI model endpoint also implements the protocol, but it is unmaintained and not recommended for use. Implementing the respective protocols is relatively straightforward. More information can be found in our class final project submission (as this work started as a class project for CS 4100 at Northeastern University).
Cite
@misc{cassano2023type,
title={Type Prediction With Program Decomposition and Fill-in-the-Type Training},
author={Federico Cassano and Ming-Ho Yee and Noah Shinn and Arjun Guha and Steven Holtzen},
year={2023},
eprint={2305.17145},
archivePrefix={arXiv},
primaryClass={cs.SE}
}
Usage
We have implemented an OpenTau in Rust, which can be utilized in three ways:
- As a simple CLI client that will type-infer a given program. (more info in
./client) - As a library, that exposes numerous abstractions for interacting with different compilers, models, and type prediction strategies. (more info in
./client) - As an evaluation tool, to analyze the performance of the combinations of different models, languages, datasets, and type prediction strategies
on the task of type prediction. (more info in
./evaluator)
We are in the review process for our paper: Type Prediction With Program Decomposition and Fill-in-the-Type Training. Federico Cassano, Ming-Ho Yee, Noah Shinn, Arjun Guha, Steven Holtzen.
Requirements
rust- Incoder/SantaCoder model requirements:
torchtokenizers>=0.12transformers
- TypeScript compiler requirements:
ts-nodetsc
- Python compiler requirements (Work in progress):
mypy|pyrightfor static type checkingredbaronfor AST parsing with comments
pandocONLY for building the report
Installation
Run make while being in the directory
The output binary (symlinked) will be at /out/client
Example completion
Our system was able to type-infer this program:
const findAllPeople = function (n, meetings, firstPerson) {
meetings.sort((a, b) => a[2] - b[2]);
const uf = new UnionFind(n);
uf.connect(0, firstPerson);
let ppl = [];
for (let i = 0, len = meetings.length; i < len; ) {
ppl = [];
let time = meetings[i][2];
while (i < len && meetings[i][2] === time) {
uf.connect(meetings[i][0], meetings[i][1]);
ppl.push(meetings[i][0]);
ppl.push(meetings[i][1]);
i++;
}
for (let n of ppl) {
if (!uf.connected(0, n)) uf.reset(n);
}
}
let ans = [];
for (let i = 0; i < n; ++i) {
if (uf.connected(0, i)) ans.push(i);
}
return ans;
};
class UnionFind {
arr;
constructor(n) {
this.arr = Array(n).fill(null);
this.arr.forEach((e, i, arr) => (arr[i] = i));
}
connect(a, b) {
this.arr[this.find(a)] = this.find(this.arr[b]);
}
find(a) {
return this.arr[a] === a ? a : (this.arr[a] = this.find(this.arr[a]));
}
connected(a, b) {
return this.find(a) === this.find(b);
}
reset(a) {
this.arr[a] = a;
}
}
And annotate it with these types:
const findAllPeople: (
n: number,
meetings: number[][],
firstPerson: number
) => number[] = function (n, meetings, firstPerson) {
meetings.sort((a, b) => a[2] - b[2]);
const uf: UnionFind = new UnionFind(n);
uf.connect(0, firstPerson);
let ppl: number[] = [];
for (let i = 0, len = meetings.length; i < len; ) {
ppl = [];
let time: number = meetings[i][2];
while (i < len && meetings[i][2] === time) {
uf.connect(meetings[i][0], meetings[i][1]);
ppl.push(meetings[i][0]);
ppl.push(meetings[i][1]);
i++;
}
for (let n of ppl) {
if (!uf.connected(0, n)) uf.reset(n);
}
}
let ans: number[] = [];
for (let i = 0; i < n; ++i) {
if (uf.connected(0, i)) ans.push(i);
}
return ans;
};
class UnionFind {
arr: number[];
constructor(n) {
this.arr = Array(n).fill(null);
this.arr.forEach((e, i, arr) => (arr[i] = i));
}
connect(a: number, b: number): void {
this.arr[this.find(a)] = this.find(this.arr[b]);
}
find(a: number): number {
return this.arr[a] === a ? a : (this.arr[a] = this.find(this.arr[a]));
}
connected(a: number, b: number): boolean {
return this.find(a) === this.find(b);
}
reset(a: number): void {
this.arr[a] = a;
}
}
While TypeScript's type inference only managed to infer these types (too many anys and loose typing):
const findAllPeople = function (n: number, meetings: any[], firstPerson: any) {
meetings.sort((a: number[], b: number[]) => a[2] - b[2]);
const uf = new UnionFind(n);
uf.connect(0, firstPerson);
let ppl = [];
for (let i = 0, len = meetings.length; i < len; ) {
ppl = [];
let time = meetings[i][2];
while (i < len && meetings[i][2] === time) {
uf.connect(meetings[i][0], meetings[i][1]);
ppl.push(meetings[i][0]);
ppl.push(meetings[i][1]);
i++;
}
for (let n of ppl) {
if (!uf.connected(0, n)) uf.reset(n);
}
}
let ans = [];
for (let i = 0; i < n; ++i) {
if (uf.connected(0, i)) ans.push(i);
}
return ans;
};
class UnionFind {
arr: any[];
constructor(n: any) {
this.arr = Array(n).fill(null);
this.arr.forEach(
(e: any, i: string | number, arr: { [x: string]: any }) => (arr[i] = i)
);
}
connect(a: number, b: string | number) {
this.arr[this.find(a)] = this.find(this.arr[b]);
}
find(a: string | number) {
return this.arr[a] === a ? a : (this.arr[a] = this.find(this.arr[a]));
}
connected(a: number, b: number) {
return this.find(a) === this.find(b);
}
reset(a: string | number) {
this.arr[a] = a;
}
}
Note that TypeScript's inference type annotated non let-bound arrow functions, while our system didn't. We believe that these functions should be left untyped, as the signature of the function that calls them should be typed, and TypeScript's type-inference should enforce those rules. Our system will not battle with TypeScript's type-inference, it will try to work alongside it. Additionally, our system will not perform any type-migrations, i.e. it will not change already defined types. This is to further enforce the coalition between our system and TypeScript's.
Another Example: Generics Inference
Our system is able to fill out generic types.
var sumFourDivisors = function (nums) {
let res = 0;
for (const e of nums) {
const set = helper(e);
if (set.size === 4) {
for (const i of set) res += i;
}
}
return res;
function helper(num) {
const set = new Set();
const r = ~~(Math.sqrt(num) + 1);
for (let i = 1; i < r; i++) {
if (num % i === 0) {
set.add(i);
set.add(num / i);
}
}
return set;
}
};
to
var sumFourDivisors: (nums: number[]) => number = function (nums) {
let res: number = 0;
for (const e of nums) {
const set: Set<number> = helper(e);
if (set.size === 4) {
for (const i of set) res += i;
}
}
return res;
function helper(num: number): Set<number> {
const set: Set<number> = new Set();
const r: number = ~~(Math.sqrt(num) + 1);
for (let i = 1; i < r; i++) {
if (num % i === 0) {
set.add(i);
set.add(num / i);
}
}
return set;
}
};
while TypeScript's inference couldn't give us a type-checkable answer:
7:28 - error TS2365: Operator '+=' cannot be applied to types 'number' and 'unknown'.
7 for (const i of set) res += i;
~~~~~~~~
Compatibility
- License
- Apache-2.0
- Crate facts
- Not measured
- Runs on, Native code and Python bindings read crates on crates.io. This entry lists no crate.
Compare with similar projects
Order: strongest tier first, then most downloads, then name, with unmeasured values last. A library counts downloads over 30 days. An app counts binary downloads per release. An app with none counts its crate downloads over 30 days. ML Rust runs this catalogue, so its closest project always gets a row.
| Project | Downloads | Dependents | Stars | Latest release | Runs on (GPU) | Native code | License |
|---|---|---|---|---|---|---|---|
|
Using Large Language Models for Repo-wide Type Prediction |
Not measured | Not applicable | 121 | Not measured | Not measured | Not measured | Apache-2.0 |
|
Self-hosted AI coding assistant |
8,774 binary downloads per release | Not applicable | 33,925 | Not measured | Not measured | Not measured | |
|
Right-size LLM models to your system hardware. Interactive TUI and CLI to match models against available RAM, CPU, and GPU. |
3,821 binary downloads per release | Not applicable | 38,787 | 1.1.20 | Metal | No native code found | MIT |
|
Ship your code, on autopilot. An open source agent that lives on your machines 24/7 and keeps your apps running. 🦀 |
19,860 binary downloads per release | Not applicable | 1,859 | 0.3.88 | GPU support unknown | Binds native code: tree-sitter, tree-sitter-language, tikv-jemalloc-sys (feature jemalloc) | Apache-2.0 |
|
LSP-AI is an open-source language server that serves as a backend for AI-powered functionality, designed to assist and empower software engineers, not replace them. |
2,314 binary downloads per release | Not applicable | 3,210 | 0.7.0 | CUDA, Metal | Binds native code: tree-sitter, tree-sitter-language, llama-cpp-sys-2 (feature cuda). Build uses a native toolchain: cc | MIT |
|
opentelemetry-langfuse Listed
OpenTelemetry components and utilities for Langfuse LLM observability |
2,119 downloads over 30 days | 2 | 4 | 0.7.2 | GPU support unknown | No native code found | Apache-2.0 OR MIT |
|
Inference server for LLMs, speech and embeddings: OpenAI-compatible routes with streaming, plus speech synthesis (TTS) and transcription (ASR). Serves Llama, Mistral, Qwen, Mamba2, MLA+MoE and hybrid models from SafeTensors, AWQ, GPTQ and GGUF. |
8 binary downloads per release | Not applicable | 7 | 0.7.1 | CUDA, WebGPU | Binds native code: ring (feature distributed) | Apache-2.0 |
Why ListedNo archive, New or Featured rule places it, so it is Listed.
What it lacks for Featured
- The project is dormant and not finished. Dormancy rule
What else the check found
- The last activity is on 10 Dec 2023, 1065 days ago. That reaches the 365 days the rules expect, so the project is dormant. Dormancy rule
Not measured: Latest release, Unpatched vulnerabilities past the grace window, Binary downloads per release, First release.
Exact rules: Listed, Featured app. The check on applied thresholds version 9.
Every rule outcome, as the check records it
expected-interval: app: E = 365 daysdormancy: last activity 2023-12-10, 1065 days ago, E = 365: dormantfeatured: not featured: dormant and not stablelisted: listed: no archive, new or featured rule applies
Measurements and sources
Each signal row shows the raw value, its source and the fetch date. The Sources list gathers every distinct source on this page. Movement needs more history.
Sources4 sources
- Description: the GitHub repository description
- GitHub repository, fetched
- GitHub releases, fetched
- GitHub contributors, fetched
AdoptionNot measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Active dependents | Not measured | Yes | ||
| New dependents (365 days) | Not measured | Yes | ||
| Peak active dependents | Not measured | Yes | ||
| Active dependents as a share of the peak | Not measured | Yes | ||
| Binary downloads per release | Not measured | Yes |
MaintenanceLatest human commit: 2 years ago8 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Latest human commit | 2023-12-10 | GitHub repository | Yes | |
| Latest release | Not measured | Yes | ||
| First release | Not measured | Yes | ||
| Latest version | Not measured | Yes | ||
| Days the oldest outside issue waits for a reply | 0 | GitHub repository | Yes | |
| Maintainer replies over 365 days | 0 | GitHub repository | Yes | |
| Repository archived | false | GitHub repository | Yes | |
| Repository gone | false | GitHub repository | Yes | |
| Release commit or manifest in the repo | Not measured | Yes | ||
| Published docs | Not measured | Yes | ||
| Latest release fails cargo check | Not measured | Yes | ||
| Build check date | Not measured | Yes | ||
| Build check toolchain | Not measured | Yes |
SecurityNot measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Unpatched vulnerabilities past the grace window | Not measured | Yes | ||
| RustSec unmaintained advisory | Not measured | Yes | ||
| Deprecation notice | Not measured | Yes | ||
| Deprecated requirement | Not measured | Yes |
Distribution0 binary downloads over the last 3 releases10 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Releases | Not measured | Yes | ||
| Downloads over 30 days | Not measured | Yes | ||
| Binary downloads over the last 3 releases | 0 | GitHub releases | Yes | |
| Homebrew installs on request over 365 days | Not measured | Yes | ||
| Repology repository families | Not measured | Yes | ||
| Docker pull growth over 90 days | Not measured | Yes | ||
| Docker pulls at the growth baseline | Not measured | Yes | ||
| Every version a prerelease | Not measured | Yes | ||
| Every version 0.0.x | Not measured | Yes | ||
| Every version yanked | Not measured | Yes | ||
| Crate deleted from crates.io | Not measured | Yes |
Repository121 stars, 7 forks, 2 contributors, 513 commits. Stars alone never qualify a project.1 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| GitHub stars | 121 | GitHub repository | Yes — one part of the repository route | |
| GitHub forks | 7 | GitHub repository | Yes — one part of the repository route | |
| Contributors with a GitHub account | 2 | GitHub contributors | Yes — one part of the repository route | |
| Commits on the default branch | 513 | GitHub repository | No | |
| Repository's main crate left out of the entry | Not measured | Yes |
Facts that never count
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| License | Apache-2.0 | GitHub repository | No | |
| Runs on, from listed crates and their direct dependencies | Not measured | No | ||
| What shows each backend | Not measured | No | ||
| Native code, from listed crates and their dependencies | Not measured | No | ||
| Python bindings, from direct dependencies of listed crates | Not measured | No | ||
| Crates that depend on a listed crate directly | Not measured | No |
Corrections and review history
No correction applies to this entry.
Judgments
A judgment enters only through an approved issue and a merged pull request.
No judgment applies to this entry.