mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Merge branch 'trunk' into hash-links
This commit is contained in:
commit
7cb414bae5
7 changed files with 658 additions and 698 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -8,14 +8,14 @@ env:
|
|||
jobs:
|
||||
prep-dependency-container:
|
||||
name: fmt, clippy, test --release
|
||||
runs-on: self-hosted
|
||||
runs-on: [self-hosted]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
clean: "false"
|
||||
clean: "true"
|
||||
|
||||
- name: Earthly version
|
||||
run: earthly --version
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -37,10 +37,11 @@ Nice collection of research on innovative editors, [link](https://futureofcoding
|
|||
* [VS code debug visualization](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer)
|
||||
* [Algorithm visualization for javascript](https://algorithm-visualizer.org)
|
||||
* [godbolt.org Compiler Explorer](https://godbolt.org/)
|
||||
* Say you have a failing test that used to work, it would be very valuable to see all code that was changed that was used only by that test.
|
||||
e.g. you have a test `calculate_sum_test` that only uses the function `add`, when the test fails you should be able to see a diff showing only what changed for the function `add`. It would also be great to have a diff of [expression values](https://homepages.cwi.nl/~storm/livelit/images/bret.png) Bret Victor style. An ambitious project would be to suggest or automatically try fixes based on these diffs.
|
||||
* [whitebox debug visualization](https://vimeo.com/483795097)
|
||||
* [Hest](https://ivanish.ca/hest-time-travel/) tool for making highly interactive simulations.
|
||||
* Say you have a failing test that used to work, it would be very valuable to see all code that was changed that was used only by that test.
|
||||
e.g. you have a test `calculate_sum_test` that only uses the function `add`, when the test fails you should be able to see a diff showing only what changed for the function `add`. It would also be great to have a diff of [expression values](https://homepages.cwi.nl/~storm/livelit/images/bret.png) Bret Victor style. An ambitious project would be to suggest or automatically try fixes based on these diffs.
|
||||
* I think it could be possible to create a minimal reproduction of a program / block of code / code used by a single test. So for a failing unit test I would expect it to extract imports, the platform, types and functions that are necessary to run only that unit test and put them in a standalone roc project. This would be useful for sharing bugs with library+application authors and colleagues, for profiling or debugging with all "clutter" removed.
|
||||
|
||||
|
||||
### Structured Editing
|
||||
|
@ -86,6 +87,12 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
|
|||
* Mozilla DeepSpeech model runs fast, works pretty well for actions but would need additional training for code input.
|
||||
Possible to reuse [Mozilla common voice](https://github.com/common-voice/common-voice) for creating more "spoken code" data.
|
||||
|
||||
### Beginner-focused Features
|
||||
|
||||
* Show Roc cheat sheet on start-up.
|
||||
* Plugin that translates short pieces of code from another programming language to Roc. [Relevant research](https://www.youtube.com/watch?v=xTzFJIknh7E). Someone who only knows the R language could get started with Roc with less friction if they could quickly define a list R style (`lst <- c(1,2,3)`) and get it translated to Roc.
|
||||
* Being able to asses or ask the user for the amount of experience they have with Roc would be a valuable feature for recommending plugins, editor tips, recommending tutorials, automated error search (e.g searching common beginner errors first), ... .
|
||||
|
||||
### Productivity features
|
||||
|
||||
* When refactoring;
|
||||
|
@ -106,6 +113,13 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
|
|||
* Regex-like find and substitution based on plain english description and example (replacement). i.e. replace all `[` between double quotes with `{`. [Inspiration](https://alexmoltzau.medium.com/english-to-regex-thanks-to-gpt-3-13f03b68236e).
|
||||
* Show productivity tips based on behavior. i.e. if the user is scrolling through the error bar and clicking on the next error several times, show a tip with "go to next error" shortcut.
|
||||
* Command to "benchmark this function" or "benchmark this test" with flamegraph and execution time per line.
|
||||
* Instead of going to definition and having to navigate back and forth between files, show an editable view inside the current file. See [this video](https://www.youtube.com/watch?v=EenznqbW5w8)
|
||||
* When encountering an unexpected error in the user's program we show a button at the bottom to start an automated search on this error. The search would:
|
||||
* look for similar errors in github issues of the relevant libraries
|
||||
* search stackoverflow questions
|
||||
* search a local history of previously encountered errors and fixes
|
||||
* search through a database of our zullip questions
|
||||
* ...
|
||||
|
||||
#### Autocomplete
|
||||
|
||||
|
@ -124,6 +138,8 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
|
|||
* [Codota](https://www.codota.com) AI autocomplete and example searching.
|
||||
* [Aroma](https://ai.facebook.com/blog/aroma-ml-for-code-recommendation) showing examples similar to current code.
|
||||
* [MISM](https://arxiv.org/abs/2006.05265) neural network based code similarity scoring.
|
||||
* [Inquisitive code editor](https://web.eecs.utk.edu/~azh/blog/inquisitivecodeeditor.html) Interactive bug detection with doc+test generation.
|
||||
* [NextJournal](https://nextjournal.com/joe-loco/command-bar?token=DpU6ewNQnLhYtVkwhs9GeX) Discoverable commands and shortcuts.
|
||||
|
||||
### Non-Code Related Inspiration
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::lang::{
|
|||
|
||||
use roc_can::expected::Expected;
|
||||
use roc_collections::all::{BumpMap, BumpMapDefault, Index};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_module::{ident::TagName, symbol::Symbol};
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_types::{
|
||||
subs::Variable,
|
||||
|
@ -21,7 +21,7 @@ use roc_types::{
|
|||
pub enum Constraint<'a> {
|
||||
Eq(Type2, Expected<Type2>, Category, Region),
|
||||
// Store(Type, Variable, &'static str, u32),
|
||||
// Lookup(Symbol, Expected<Type>, Region),
|
||||
Lookup(Symbol, Expected<Type2>, Region),
|
||||
// Pattern(Region, PatternCategory, Type, PExpected<Type>),
|
||||
And(BumpVec<'a, Constraint<'a>>),
|
||||
Let(&'a LetConstraint<'a>),
|
||||
|
@ -52,6 +52,7 @@ pub fn constrain_expr<'a>(
|
|||
Expr2::SmallStr(_) => Eq(str_type(env.pool), expected, Category::Str, region),
|
||||
Expr2::Blank => True,
|
||||
Expr2::EmptyRecord => constrain_empty_record(expected, region),
|
||||
Expr2::Var(symbol) => Lookup(*symbol, expected, region),
|
||||
Expr2::SmallInt { var, .. } => {
|
||||
let mut flex_vars = BumpVec::with_capacity_in(1, arena);
|
||||
|
||||
|
@ -216,6 +217,65 @@ pub fn constrain_expr<'a>(
|
|||
exists(arena, field_vars, And(constraints))
|
||||
}
|
||||
}
|
||||
Expr2::GlobalTag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments,
|
||||
} => {
|
||||
let mut flex_vars = BumpVec::with_capacity_in(arguments.len(), arena);
|
||||
let types = PoolVec::with_capacity(arguments.len() as u32, env.pool);
|
||||
let mut arg_cons = BumpVec::with_capacity_in(arguments.len(), arena);
|
||||
|
||||
for (argument_node_id, type_node_id) in
|
||||
arguments.iter_node_ids().zip(types.iter_node_ids())
|
||||
{
|
||||
let (var, expr_node_id) = env.pool.get(argument_node_id);
|
||||
|
||||
let argument_expr = env.pool.get(*expr_node_id);
|
||||
|
||||
let arg_con = constrain_expr(
|
||||
arena,
|
||||
env,
|
||||
argument_expr,
|
||||
Expected::NoExpectation(Type2::Variable(*var)),
|
||||
region,
|
||||
);
|
||||
|
||||
arg_cons.push(arg_con);
|
||||
flex_vars.push(*var);
|
||||
|
||||
env.pool[type_node_id] = Type2::Variable(*var);
|
||||
}
|
||||
|
||||
let union_con = Eq(
|
||||
Type2::TagUnion(
|
||||
PoolVec::new(std::iter::once((*name, types)), env.pool),
|
||||
env.pool.add(Type2::Variable(*ext_var)),
|
||||
),
|
||||
expected.shallow_clone(),
|
||||
Category::TagApply {
|
||||
tag_name: TagName::Global(name.as_str(env.pool).into()),
|
||||
args_count: arguments.len(),
|
||||
},
|
||||
region,
|
||||
);
|
||||
|
||||
let ast_con = Eq(
|
||||
Type2::Variable(*variant_var),
|
||||
expected,
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
region,
|
||||
);
|
||||
|
||||
flex_vars.push(*variant_var);
|
||||
flex_vars.push(*ext_var);
|
||||
|
||||
arg_cons.push(union_con);
|
||||
arg_cons.push(ast_con);
|
||||
|
||||
exists(arena, flex_vars, And(arg_cons))
|
||||
}
|
||||
_ => todo!("implement constaints for {:?}", expr),
|
||||
}
|
||||
}
|
||||
|
@ -268,13 +328,7 @@ fn empty_list_type(pool: &mut Pool, var: Variable) -> Type2 {
|
|||
|
||||
#[inline(always)]
|
||||
fn list_type(pool: &mut Pool, typ: Type2) -> Type2 {
|
||||
let args = PoolVec::with_capacity(1, pool);
|
||||
|
||||
for (arg_node_id, arg) in args.iter_node_ids().zip(vec![typ]) {
|
||||
pool[arg_node_id] = arg;
|
||||
}
|
||||
|
||||
builtin_type(Symbol::LIST_LIST, args)
|
||||
builtin_type(Symbol::LIST_LIST, PoolVec::new(vec![typ].into_iter(), pool))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
use crate::lang::constrain::Constraint::{self, *};
|
||||
use crate::lang::pool::Pool;
|
||||
use crate::lang::pool::{Pool, ShallowClone};
|
||||
use crate::lang::types::Type2;
|
||||
use bumpalo::Bump;
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
|
@ -270,75 +270,79 @@ fn solve<'a>(
|
|||
// }
|
||||
// }
|
||||
// }
|
||||
// Lookup(symbol, expectation, region) => {
|
||||
// match env.vars_by_symbol.get(&symbol) {
|
||||
// Some(var) => {
|
||||
// // Deep copy the vars associated with this symbol before unifying them.
|
||||
// // Otherwise, suppose we have this:
|
||||
// //
|
||||
// // identity = \a -> a
|
||||
// //
|
||||
// // x = identity 5
|
||||
// //
|
||||
// // When we call (identity 5), it's important that we not unify
|
||||
// // on identity's original vars. If we do, the type of `identity` will be
|
||||
// // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect;
|
||||
// // the type of `identity` is more general than that!
|
||||
// //
|
||||
// // Instead, we want to unify on a *copy* of its vars. If the copy unifies
|
||||
// // successfully (in this case, to `Int -> Int`), we can use that to
|
||||
// // infer the type of this lookup (in this case, `Int`) without ever
|
||||
// // having mutated the original.
|
||||
// //
|
||||
// // If this Lookup is targeting a value in another module,
|
||||
// // then we copy from that module's Subs into our own. If the value
|
||||
// // is being looked up in this module, then we use our Subs as both
|
||||
// // the source and destination.
|
||||
// let actual = deep_copy_var(subs, rank, pools, *var);
|
||||
// let expected = type_to_var(
|
||||
// subs,
|
||||
// rank,
|
||||
// pools,
|
||||
// cached_aliases,
|
||||
// expectation.get_type_ref(),
|
||||
// );
|
||||
// match unify(subs, actual, expected) {
|
||||
// Success(vars) => {
|
||||
// introduce(subs, rank, pools, &vars);
|
||||
Lookup(symbol, expectation, region) => {
|
||||
match env.vars_by_symbol.get(&symbol) {
|
||||
Some(var) => {
|
||||
// Deep copy the vars associated with this symbol before unifying them.
|
||||
// Otherwise, suppose we have this:
|
||||
//
|
||||
// state
|
||||
// }
|
||||
// identity = \a -> a
|
||||
//
|
||||
// Failure(vars, actual_type, expected_type) => {
|
||||
// introduce(subs, rank, pools, &vars);
|
||||
// x = identity 5
|
||||
//
|
||||
// let problem = TypeError::BadExpr(
|
||||
// *region,
|
||||
// Category::Lookup(*symbol),
|
||||
// actual_type,
|
||||
// expectation.clone().replace(expected_type),
|
||||
// );
|
||||
// When we call (identity 5), it's important that we not unify
|
||||
// on identity's original vars. If we do, the type of `identity` will be
|
||||
// mutated to be `Int -> Int` instead of `a -> `, which would be incorrect;
|
||||
// the type of `identity` is more general than that!
|
||||
//
|
||||
// problems.push(problem);
|
||||
// Instead, we want to unify on a *copy* of its vars. If the copy unifies
|
||||
// successfully (in this case, to `Int -> Int`), we can use that to
|
||||
// infer the type of this lookup (in this case, `Int`) without ever
|
||||
// having mutated the original.
|
||||
//
|
||||
// state
|
||||
// }
|
||||
// BadType(vars, problem) => {
|
||||
// introduce(subs, rank, pools, &vars);
|
||||
//
|
||||
// problems.push(TypeError::BadType(problem));
|
||||
//
|
||||
// state
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// None => {
|
||||
// problems.push(TypeError::UnexposedLookup(*symbol));
|
||||
//
|
||||
// state
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// If this Lookup is targeting a value in another module,
|
||||
// then we copy from that module's Subs into our own. If the value
|
||||
// is being looked up in this module, then we use our Subs as both
|
||||
// the source and destination.
|
||||
let actual = deep_copy_var(subs, rank, pools, *var);
|
||||
|
||||
let expected = type_to_var(
|
||||
arena,
|
||||
mempool,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
cached_aliases,
|
||||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
match unify(subs, actual, expected) {
|
||||
Success(vars) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
*region,
|
||||
Category::Lookup(*symbol),
|
||||
actual_type,
|
||||
expectation.shallow_clone().replace(expected_type),
|
||||
);
|
||||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
problems.push(TypeError::UnexposedLookup(*symbol));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
And(sub_constraints) => {
|
||||
let mut state = state;
|
||||
|
||||
|
@ -826,7 +830,7 @@ fn type_to_variable<'a>(
|
|||
let mut tag_vars = MutMap::default();
|
||||
let ext = mempool.get(*ext_id);
|
||||
|
||||
for (_tag, tag_argument_types) in tags.iter(mempool) {
|
||||
for (tag, tag_argument_types) in tags.iter(mempool) {
|
||||
let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len());
|
||||
|
||||
for arg_type in tag_argument_types.iter(mempool) {
|
||||
|
@ -836,7 +840,7 @@ fn type_to_variable<'a>(
|
|||
}
|
||||
|
||||
tag_vars.insert(
|
||||
roc_module::ident::TagName::Private(Symbol::NUM_NUM),
|
||||
roc_module::ident::TagName::Global(tag.as_str(mempool).into()),
|
||||
tag_argument_vars,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -238,3 +238,15 @@ fn constrain_list_of_records() {
|
|||
"List { x : Num * }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrain_global_tag() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo
|
||||
"#
|
||||
),
|
||||
"[ Foo ]*",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ In Elm:
|
|||
In Roc:
|
||||
|
||||
```
|
||||
{ x : name : Str, email : Str }* -> Str
|
||||
{ name : Str, email : Str }* -> Str
|
||||
```
|
||||
|
||||
Here, the open record's type variable appears immediately after the `}`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue