Skip to content

Implement context feature. #783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/source/Regexp.mint
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,6 @@ module Regexp {
Regexp.exec(/,/, "a,b,c,d") == [","]
*/
fun exec (regexp : Regexp, string : String) : Array(String) {
`[...#{regexp}.exec(#{string})] || []`
`[...(#{regexp}.exec(#{string}) || [])] || []`
}
}
33 changes: 33 additions & 0 deletions core/tests/tests/Context.mint
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
type Test.Context {
name : String
}

component Test.NestedConsumer {
fun render {
<Test.Consumer/>
}
}

component Test.Consumer {
context ctx : Test.Context

fun render {
<div>ctx.name</div>
}
}

component Test.Provider {
provide Test.Context { name: "Joe" }

fun render : Html {
<Test.NestedConsumer/>
}
}

suite "Context" {
test "Test.Provider" {
<Test.Provider/>
|> Test.Html.start()
|> Test.Html.assertTextOf("div", "Joe")
}
}
4 changes: 2 additions & 2 deletions runtime/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { createElement, Fragment as fragment } from "preact";
export { useEffect, useMemo, useRef } from "preact/hooks";
export { createElement, Fragment as fragment, createContext } from "preact";
export { useEffect, useMemo, useRef, useContext } from "preact/hooks";
export { signal, batch } from "@preact/signals";

export * from "./src/pattern_matching";
Expand Down
100 changes: 100 additions & 0 deletions spec/compilers/context
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
type Form {
set: Function(String, String, Promise(Void)),
get: Function(String, String)
}

type Maybe(a) {
Nothing
Just(a)
}

type Result(err, value) {
Ok(value)
Err(err)
}

component Input {
property name : String
context form : Form

fun handleClick {
form.set(name, form.get(name) + "1")
}

fun render {
<button onClick={handleClick}>
"Change!"
</button>
}
}

component Main {
state form : Map(String, String) = {} of String => String

provide Form {
set: (name : String, value : String) { next { form: form } },
get: (name : String) { form[name] or "" }
}

fun render {
<div>
<Input name="firstname"/>
<Input name="lastname"/>
</div>
}
}
--------------------------------------------------------------------------------
import {
createElement as E,
createContext as B,
useContext as D,
mapAccess as H,
useSignal as F,
variant as A,
record as C,
or as G
} from "./runtime.js";

export const
I = A(0, `Maybe.Nothing`),
J = A(1, `Maybe.Just`),
K = A(1, `Result.Ok`),
L = A(1, `Result.Err`),
M = B(),
a = C(`Form`),
N = ({
b
}) => {
const
c = () => {
return d.set(b, d.get(b) + `1`)
},
d = D(M);
return E(`button`, {
"onClick": c
}, [`Change!`])
},
O = () => {
const e = F([]);
return E(M.Provider, {
value: a({
set: (f, g) => {
return (() => {
e.value = e.value
})()
},
get: (h) => {
return G(I, L, H(e.value, h, J, I), ``)
}
})
}, (() => {
return E(`div`, {}, [
E(N, {
b: `firstname`
}),
E(N, {
b: `lastname`
})
])
})())
};
12 changes: 12 additions & 0 deletions spec/errors/context_expected_colon
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
component Test {
context test
--------------------------------------------------------------------------------
░ ERROR (CONTEXT_EXPECTED_COLON) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

I was expecting the colon of a context but I found "a space" instead:

┌ errors/context_expected_colon:2:14
├───────────────────────────────────
1│ component Test {
2│ context test
│ ⌃⌃⌃⌃
12 changes: 12 additions & 0 deletions spec/errors/context_expected_name
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
component Test {
context
--------------------------------------------------------------------------------
░ ERROR (CONTEXT_EXPECTED_NAME) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

I was expecting the name of a context but I found "a space" instead:

┌ errors/context_expected_name:2:9
├─────────────────────────────────
1│ component Test {
2│ context
│ ⌃⌃⌃⌃
12 changes: 12 additions & 0 deletions spec/errors/context_expected_type
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
component Test {
context test :
--------------------------------------------------------------------------------
░ ERROR (CONTEXT_EXPECTED_TYPE) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

I was expecting the type of a context but I found "a space" instead:

┌ errors/context_expected_type:2:16
├──────────────────────────────────
1│ component Test {
2│ context test :
│ ⌃⌃⌃⌃
31 changes: 31 additions & 0 deletions spec/errors/context_expected_type_not_found
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
component Test {
context name : Data

fun render : Html {
<div/>
}
}

component Main {
fun render : Html {
<Test/>
}
}
--------------------------------------------------------------------------------
░ ERROR (CONTEXT_TYPE_NOT_FOUND) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

I was looking for a type for a context but I can't find it:

Data

The context in question is here:

┌ errors/context_expected_type_not_found:2:3
├───────────────────────────────────────────
1│ component Test {
2│ context name : Data
│ ⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃
3│
4│ fun render : Html {
5│ <div/>
6│ }
49 changes: 49 additions & 0 deletions spec/errors/context_not_provided_for
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
type Data {
set : String
}

component Test {
context name : Data

fun render : Html {
<div/>
}
}

component Main {
fun render : Html {
<Test/>
}
}
--------------------------------------------------------------------------------
░ ERROR (CONTEXT_NOT_PROVIDED_FOR) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

A value is not provided for this context in an parent component.

The context in question is here:

┌ errors/context_not_provided_for:6:3
├────────────────────────────────────
2│ set : String
3│ }
4│
5│ component Test {
6│ context name : Data
│ ⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃
7│
8│ fun render : Html {
9│ <div/>
10│ }

The component was used here:

┌ errors/context_not_provided_for:15:5
├─────────────────────────────────────
11│ }
12│
13│ component Main {
14│ fun render : Html {
15│ <Test/>
│ ⌃⌃⌃⌃⌃⌃⌃
16│ }
17│ }
53 changes: 53 additions & 0 deletions spec/errors/context_not_record
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
type Data {
X
}

component Test {
context name : Data

fun render : Html {
<div/>
}
}

component Main {
fun render : Html {
<Test/>
}
}
--------------------------------------------------------------------------------
░ ERROR (CONTEXT_NOT_RECORD) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

The type of a context is not a record:

Data

The context in question is here:

┌ errors/context_not_record:6:3
├──────────────────────────────
2│ X
3│ }
4│
5│ component Test {
6│ context name : Data
│ ⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃
7│
8│ fun render : Html {
9│ <div/>
10│ }

The type is defined here:

┌ errors/context_not_record:1:1
├──────────────────────────────
│ ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
1│ type Data {
2│ X
3│ }
│ ⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃
4│
5│ component Test {
6│ context name : Data
7│

59 changes: 59 additions & 0 deletions spec/examples/context
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---------------------------------------------------------context_expected_name
component Test {
context
---------------------------------------------------------context_expected_colon
component Test {
context test
---------------------------------------------------------context_expected_type
component Test {
context test :
---------------------------------------------------------context_type_not_found
component Test {
context name : Data

fun render : Html {
<div/>
}
}

component Main {
fun render : Html {
<Test/>
}
}
--------------------------------------------------------------context_not_record
type Data {
X
}

component Test {
context name : Data

fun render : Html {
<div/>
}
}

component Main {
fun render : Html {
<Test/>
}
}
-------------------------------------------------------context_not_provided_for
type Data {
set : String
}

component Test {
context name : Data

fun render : Html {
<div/>
}
}

component Main {
fun render : Html {
<Test/>
}
}
Loading