Merge branch 'main' into pr/212

This commit is contained in:
Shunsuke Shibayama 2022-11-13 19:57:20 +09:00
commit a4d5b09df9
14 changed files with 242 additions and 114 deletions

View file

@ -753,6 +753,9 @@ impl PyCodeGenerator {
}
fn cancel_if_pop_top(&mut self) {
if self.cur_block_codeobj().code.len() < 2 {
return;
}
let lasop_t_idx = self.cur_block_codeobj().code.len() - 2;
if self.cur_block_codeobj().code.get(lasop_t_idx) == Some(&(POP_TOP as u8)) {
self.mut_cur_block_codeobj().code.pop();
@ -1495,7 +1498,7 @@ impl PyCodeGenerator {
} else {
// no else block
let idx_end = if self.py_version.minor >= Some(11) {
self.lasti() - idx_pop_jump_if_false
self.lasti() - idx_pop_jump_if_false - 1
} else {
self.lasti()
};
@ -2376,14 +2379,15 @@ impl PyCodeGenerator {
log!(info "entered {}", fn_name!());
let line = sig.ln_begin().unwrap();
let class_name = sig.ident().inspect();
let ident = Identifier::public_with_line(DOT, Str::ever("__init__"), line);
let mut ident = Identifier::public_with_line(DOT, Str::ever("__init__"), line);
ident.vi.t = __new__.clone();
let param_name = fresh_varname();
let param = VarName::from_str_and_line(Str::from(param_name.clone()), line);
let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None);
let self_param = VarName::from_str_and_line(Str::ever("self"), line);
let self_param = NonDefaultParamSignature::new(ParamPattern::VarName(self_param), None);
let params = Params::new(vec![self_param, param], None, vec![], None);
let subr_sig = SubrSignature::new(ident, params, __new__.clone());
let subr_sig = SubrSignature::new(ident, params);
let mut attrs = vec![];
match __new__.non_default_params().unwrap()[0].typ() {
// namedtupleは仕様上::xなどの名前を使えない
@ -2431,11 +2435,12 @@ impl PyCodeGenerator {
log!(info "entered {}", fn_name!());
let class_ident = sig.ident();
let line = sig.ln_begin().unwrap();
let ident = Identifier::public_with_line(DOT, Str::ever("new"), line);
let mut ident = Identifier::public_with_line(DOT, Str::ever("new"), line);
ident.vi.t = __new__;
let param_name = fresh_varname();
let param = VarName::from_str_and_line(Str::from(param_name.clone()), line);
let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None);
let sig = SubrSignature::new(ident, Params::new(vec![param], None, vec![], None), __new__);
let sig = SubrSignature::new(ident, Params::new(vec![param], None, vec![], None));
let arg = PosArg::new(Expr::Accessor(Accessor::private_with_line(
Str::from(param_name),
line,

View file

@ -3,7 +3,7 @@ use std::option::Option; // conflicting to Type::Option
use erg_common::error::MultiErrorDisplay;
use crate::ty::constructors::{and, or};
use crate::ty::constructors::{and, or, poly};
use crate::ty::free::fresh_varname;
use crate::ty::free::{Constraint, FreeKind};
use crate::ty::typaram::{OpKind, TyParam, TyParamOrdering};
@ -848,6 +848,35 @@ impl Context {
let t = self.into_refinement(t.clone());
Type::Refinement(self.union_refinement(&t, r))
}
// Array({1, 2}, 2), Array({3, 4}, 2) ==> Array({1, 2, 3, 4}, 2)
(
Type::Poly {
name: ln,
params: lps,
},
Type::Poly {
name: rn,
params: rps,
},
) if ln == rn => {
debug_assert_eq!(lps.len(), rps.len());
let mut unified_params = vec![];
for (lp, rp) in lps.iter().zip(rps.iter()) {
match (lp, rp) {
(TyParam::Type(l), TyParam::Type(r)) => {
unified_params.push(TyParam::t(self.union(l, r)))
}
(_, _) => {
if self.eq_tp(lp, rp) {
unified_params.push(lp.clone());
} else {
return or(lhs.clone(), rhs.clone());
}
}
}
}
poly(ln, unified_params)
}
(l, r) => or(l.clone(), r.clone()),
}
}

View file

@ -686,8 +686,11 @@ impl Context {
self.deref_tyvar(mem::take(var.ref_mut_t()), Covariant, var.loc())?;
}
hir::Signature::Subr(subr) => {
subr.t =
self.deref_tyvar(mem::take(&mut subr.t), Covariant, subr.loc())?;
*subr.ref_mut_t() = self.deref_tyvar(
mem::take(subr.ref_mut_t()),
Covariant,
subr.loc(),
)?;
}
}
for chunk in attr.body.block.iter_mut() {
@ -738,8 +741,11 @@ impl Context {
self.deref_tyvar(mem::take(var.ref_mut_t()), Covariant, var.loc())?;
}
hir::Signature::Subr(subr) => {
subr.t =
self.deref_tyvar(mem::take(&mut subr.t), Covariant, subr.loc())?;
*subr.ref_mut_t() = self.deref_tyvar(
mem::take(subr.ref_mut_t()),
Covariant,
subr.loc(),
)?;
}
}
for chunk in def.body.block.iter_mut() {

View file

@ -437,6 +437,11 @@ impl Identifier {
self.name.inspect()
}
/// show dot + name (no qual_name & type)
pub fn show(&self) -> String {
format!("{}{}", fmt_option!(self.dot), self.name)
}
pub fn is_procedural(&self) -> bool {
self.name.is_procedural()
}
@ -1366,22 +1371,45 @@ impl Params {
pub struct SubrSignature {
pub ident: Identifier,
pub params: Params,
pub t: Type,
}
impl NestedDisplay for SubrSignature {
fn fmt_nest(&self, f: &mut fmt::Formatter<'_>, _level: usize) -> fmt::Result {
write!(f, "{}{} (: {})", self.ident, self.params, self.t,)
write!(
f,
"{}{} (: {})",
self.ident.show(),
self.params,
self.ident.t()
)
}
}
impl_display_from_nested!(SubrSignature);
impl_locational!(SubrSignature, ident, params);
impl_t!(SubrSignature);
impl HasType for SubrSignature {
#[inline]
fn ref_t(&self) -> &Type {
self.ident.ref_t()
}
#[inline]
fn ref_mut_t(&mut self) -> &mut Type {
self.ident.ref_mut_t()
}
#[inline]
fn signature_t(&self) -> Option<&Type> {
self.ident.signature_t()
}
#[inline]
fn signature_mut_t(&mut self) -> Option<&mut Type> {
self.ident.signature_mut_t()
}
}
impl SubrSignature {
pub const fn new(ident: Identifier, params: Params, t: Type) -> Self {
Self { ident, params, t }
pub const fn new(ident: Identifier, params: Params) -> Self {
Self { ident, params }
}
pub fn is_procedural(&self) -> bool {

View file

@ -988,8 +988,9 @@ impl ASTLowerer {
id,
found_body_t,
)?;
let ident = hir::Identifier::bare(sig.ident.dot, sig.ident.name);
let sig = hir::SubrSignature::new(ident, params, t);
let mut ident = hir::Identifier::bare(sig.ident.dot, sig.ident.name);
ident.vi.t = t;
let sig = hir::SubrSignature::new(ident, params);
let body = hir::DefBody::new(body.op, block, body.id);
Ok(hir::Def::new(hir::Signature::Subr(sig), body))
}
@ -1019,7 +1020,7 @@ impl ASTLowerer {
);
let block = self.lower_block(body.block)?;
let ident = hir::Identifier::bare(sig.ident.dot, sig.ident.name);
let sig = hir::SubrSignature::new(ident, params, Type::Failure);
let sig = hir::SubrSignature::new(ident, params);
let body = hir::DefBody::new(body.op, block, body.id);
Ok(hir::Def::new(hir::Signature::Subr(sig), body))
}

View file

@ -273,7 +273,14 @@ impl Parser {
chunks.push(expr);
}
}
None => switch_unreachable!(),
None => {
if !self.errs.is_empty() {
self.level -= 1;
return Err(());
} else {
switch_unreachable!()
}
}
}
}
self.level -= 1;

View file

@ -29,14 +29,27 @@ That is, expressions such as `X**2 - 5X + 6 == 0` cannot be used as refinement-t
If you know how to solve quadratic equations, you would expect the above refinement form to be equivalent to `{2, 3}`.
However, the Erg compiler has very little knowledge of algebra, so it cannot solve the predicate on the right.
## Subtyping rules for refinement types
All refinement types are subtypes of the type specified in the `Type` part.
```erg
{I: Int | I <= 0} <: Int
```
Otherwise, the current Erg has a subtyping type rule for integer comparisons.
```erg
{I: Int | I <= 5} <: {I: Int | I <= 0}
```
## Smart Cast
It's nice that you defined `Odd`, but as it is, it doesn't look like it can be used much outside of literals. To promote an odd number in a normal `Int` object to `Odd`, i.e., to downcast an `Int` to `Odd`, you need to pass the constructor of `Odd`.
For refinement types, the normal constructor `.new` may panic, and there is an auxiliary constructor called `.try_new` that returns a `Result` type.
```python
i = Odd.new (0..10).sample!()
i: Odd # or Panic
i = Odd.new (0..10).sample!() # i: Odd (or Panic)
```
It can also be used as a type specification in `match`.

View file

@ -2,6 +2,8 @@
[![badge](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fgezf7g7pd5.execute-api.ap-northeast-1.amazonaws.com%2Fdefault%2Fsource_up_to_date%3Fowner%3Derg-lang%26repos%3Derg%26ref%3Dmain%26path%3Ddoc/EN/API/funcs.md%26commit_hash%3D06f8edc9e2c0cee34f6396fd7c64ec834ffb5352)](https://gezf7g7pd5.execute-api.ap-northeast-1.amazonaws.com/default/source_up_to_date?owner=erg-lang&repos=erg&ref=main&path=doc/EN/API/funcs.md&commit_hash=06f8edc9e2c0cee34f6396fd7c64ec834ffb5352)
> __Note__: `match`は関数ではなく特殊形式です。
## 基本関数
### if|T; U|(cond: Bool, then: T, else: U) -> T or U

View file

@ -53,46 +53,21 @@ if True, do:
無名プロシージャ、プロシージャ型を生成する。
## `:`(subject, T)
subjectがTに合致しているか判定する。合致していない場合はコンパイルエラーを送出する。
```python
a: Int
f x: Int, y: Int = x / y
```
また、`:`適用スタイルにも使われる。
```python
f x:
y
z
```
`:``=`と同じく演算の結果が未定義である。
```python
_ = x: Int # SyntaxError:
print!(x: Int) # SyntaxError:
```
## `.`(obj, attr)
objの属性を読み込む。
`x.[y, z]`とすると、xのyとzという属性を配列にして返す。
## `|>`(obj, c: Callable)
`c(obj)`を実行する。`x + y |>.foo()``(x + y).foo()`と同じ。
### (x: Option T)`?` -> T | T
### (x: Option T)`?` -> T
後置演算子。`x.unwrap()`を呼び出し、エラーの場合はその場で`return`する。
## match(obj, ...lambdas: Lambda)
## match(obj, ...arms: Lambda)
objについて、パターンにマッチしたlambdaを実行する
objについて、パターンにマッチしたarmを実行する。armは無名関数でなくてはならない。
```python
match [1, 2, 3]:
@ -102,15 +77,25 @@ match [1, 2, 3]:
# (1, 2, 3)
```
## del(x: ...T) -> NoneType | T
型指定によって処理を分岐できるが、型推論の結果は分岐に影響しない。
```python
zero: {0} -> {0}
one: {1} -> {1}
_ = match x:
i -> zero i
j -> one j # Warning: cannot reach this arm
```
## Del(x: ...T) -> NoneType
変数`x`を削除する。ただし組み込みのオブジェクトは削除できない。
```python
a = 1
del a # OK
Del a # OK
del True # SyntaxError: cannot delete a built-in object
Del True # SyntaxError: cannot delete a built-in object
```
## do(body: Body) -> Func
@ -149,7 +134,7 @@ assert True.then(choice) == 1
### `{}`(layout, ...names, ...preds)
篩型、ランク2型を生成する。
篩型を生成する。
### `...`
@ -169,10 +154,10 @@ assert {x; ...yz} == {x = 1; y = 2; z = 3}
ユーザーが直接使用できない演算子です。
### ref(x: T) -> Ref T | T
### ref(x: T) -> Ref T
オブジェクトの不変参照を返す。
### ref!(x: T!) -> Ref! T! | T!
### ref!(x: T!) -> Ref! T!
可変オブジェクトの可変参照を返す。

View file

@ -8,13 +8,13 @@
基本的に、すべてのパターンは「平坦化」される。具体的には、
```
```erg
[i, [j, k]] -> ...
```
は、
```
```erg
%1 ->
i = %1[0]
%2 = %1[1]
@ -46,7 +46,7 @@ Typeが篩型(e.g. `{I: Int | I >= 0}`)の場合は、中の述語式を評価
`F(X)`型の検査を考える。定義が`F(A <-> E) = ...`(`E`中に現れる`Self`はオブジェクト自身に置換される)であるとき、`fits(X, E)`で検査される。
```
```erg
fits X, E =
if Typeof(X) == Type:
then := do subtypeof X, E
@ -55,7 +55,7 @@ fits X, E =
`Array`の定義は
```
```erg
Array T <-> Union Self.iter().map(Typeof), N <-> Self.__len__() = ...
```
@ -79,55 +79,69 @@ match x:
match x:
_: {1} -> log "one"
_: {"a"} -> log "a"
_: Obj -> log "other"
_ -> log "other"
```
Ergの型付けは基本的にextrinsic(外在的)であり、型を消去したコードも動的には実行できる(型なしで項が意味を持ちうる)。これは、ErgがPythonとの相互運用性を企図して設計されているためである。
しかしパターンマッチにおいては、Ergの型はintrinsic(内在的)であるといえる。型によって処理が変わるため、型消去したコードは最早実行できない。
Ergの型は基本的にextrinsic(外在的)であり、型を消去したコードも動的には実行できる(型なしで項が意味を持ちうる)。これは、ErgがPythonとの相互運用性を企図して設計されているためである。
しかしパターンマッチにおいては、Ergの型はintrinsic(内在的)であるといえる。型指定自体がパターンの一つであり、型によって処理が変わるため、型消去したコードは最早実行できない。
## 配列・タプルパターン
## タプルパターン
```erg
(True, 1) -> ...
```
```erg
%1: ({True}, {1}) ->
_ = %1[0]
_ = %1[1]
...
```
## 配列パターン
配列は型情報が平坦化されてしまうので、一工夫が必要である。具体的には、篩型を使う。
```erg
[[True, False], [True, True]] -> ...
```
```
%1 ->
%2 = %1[0]
_: {True} = %2[0]
_: {False} = %2[1]
%3 = %1[1]
_: {True} = %3[0]
_: {True} = %3[1]
```erg
%1: {I: ? | } ->
...
```
全ての変数の型を推論すると
```
%1: [[{True}, {False}], [{True}, {True}]] ->
%2 [{True}, {False}] = %1[0]
_: {True} = %2[0]
_: {False} = %2[1]
%3: [{True}, {True}] = %1[1]
_: {True} = %3[0]
_: {True} = %3[1]
...
```
コンパイルする場合は、`%1[0] == True and %1[1] == False and ...`というように、配列の要素を直接比較することになる。
```
[True, False, i] -> ...
%1: [{True}, {False}, Obj] ->
_: {True} = %1[0]
_: {False} = %1[1]
i = %1[2]
```erg
%1: {I: [[_; ?]; ?] | } ->
%2 = %1[0]
...
```
バイトコードでの検査は`%1`の各要素に対してこれまでに見た検査を行い、`and`を取る。
`_: {True}`なら`_ == True``i`は検査なしで束縛するから、`%1[0] == True and %1[1] == False`というようになる。
```erg
%1: {I: [[_; 2]; ?] | I[0][0] == True and I[0][1] == False} ->
%2 = %1[0]
_ = %2[0]
_ = %2[1]
...
```
```erg
%1: {I: [[_; 2]; 2] | I[0][0] == True and I[0][1] == False and I[1][0] == True and I[1][1] == True} ->
%2 = %1[0]
_ = %2[0]
_ = %2[1]
%3 = %1[1]
_ = %3[0]
_ = %3[1]
...
```

View file

@ -9,23 +9,19 @@
```python
# 基本的な代入
i = 1
# 型つき
i: Int = 1
# 無名型つき
i: {1, 2, 3} = 2
# function
fn x = x + 1
# equals
fn x: Add(Int) = x + 1
# (無名)関数
fn = x -> x + 1
fn: Int -> Int = x -> x + 1
```
# 上位型
a: [Int; 4] = [0, 1, 2, 3]
# もしくは
a: Array Int, 4 = [0, 1, 2, 3]
## 型宣言パターン
```
i: Int = 1
j: {1, 2, 3} = 2
(k: Int, s: Str) = 1, "a"
```
### リテラルパターン

View file

@ -4,8 +4,8 @@
Refinement type(篩型、ふるいがた)は、述語式によって制約付けられた型です。列挙型や区間型は篩型の一種です。
篩型の標準形は`{Elem: Type | (Pred)*}`です。これは、`Pred`を満たす`Elem`を要素とする型である、という意味です。
篩型に使えるのは[Value型](./08_value.md)のみです。
篩型の標準形は`{Elem: Type | (Pred)*}`です。これは、述語式`Pred`を満たす`Elem`を要素とする型である、という意味です。
`Type`に使えるのは[Value型](./08_value.md)のみです。
```python
Nat = 0.._
@ -31,14 +31,27 @@ Array3OrMore == {A: Array _, N | N >= 3}
あなたが二次方程式の解法を知っているならば、上の篩型は`{2, 3}`と同等になるだろうと予想できるはずです。
しかしErgコンパイラは代数学の知識をほとんど持ち合わせていないので、右の述語式を解決できないのです。
## 篩型の部分型付け規則
全ての篩型は、`Type`部で指定された型の部分型です。
```erg
{I: Int | I <= 0} <: Int
```
その他、現在のErgは整数の比較に関する部分型規則を持っています。
```erg
{I: Int | I <= 5} <: {I: Int | I <= 0}
```
## スマートキャスト
`Odd`を定義したのはいいですが、このままではリテラル以外ではあまり使えないようにみえます。通常の`Int`オブジェクトの中の奇数を`Odd`に昇格させる、つまり`Int``Odd`にダウンキャストするためには、`Odd`のコンストラクタを通す必要があります。
篩型の場合、通常のコンストラクタ`.new`はパニックする可能性があり、`.try_new`という`Result`型を返す補助的なコンストラクタもあります。
```python
i = Odd.new (0..10).sample!()
i: Odd # or Panic
i = Odd.new (0..10).sample!() # i: Odd (or Panic)
```
また、`match`中で型指定として使用することもできます。
@ -73,8 +86,9 @@ match i:
```python
# メソッド.mは長さ3以上の配列に定義される
Array(T, N | N >= 3)
.m(&self) = ...
.m(ref self) = ...
```
<p align='center'>
<a href='./11_enum.md'>Previous</a> | <a href='./13_algebraic.md'>Next</a>
</p>

View file

@ -74,6 +74,36 @@ MyArray(T, N) = Inherit [T; N]
# .arrayと連動してself: Self(T, N)の型が変わる
MyStruct!(T, N: Nat!) = Class {.array: [T; !N]}
```
## 実体指定
動的配列`arr: [T; !N]`について、処理を進めていくうちに`N`の情報が失われてしまったとします。
この情報は`assert arr.__len__() == X`とすることで回復させることができます。
```erg
arr: [Int; !_]
assert arr.__len__() == 3
arr: [Int; !3]
```
これは型パラメータの __実体指定__ によって可能となっています。配列型`Array(T, N)`は以下のように定義されています。
```erg
Array T <-> Union Self.map(x -> Typeof x), N <-> Self.__len__() = ...
```
`<->`は依存型のパラメータのみで使える特別な記号で、そのパラメータに対する実体を指示します。実体であるところの右辺式は、コンパイル時に計算可能でなくても構いません。コンパイル時情報である`N`と実行時情報である`Self.__len__()`が実体指定を通してリンクされる訳です。
実体指定に沿った方法でassertionが行われると、型パラメータの情報が復活します。すなわち、`assert arr.__len__() == N`とすると`N`の情報が復活します。ただしこの場合の`N`はコンパイル時計算可能でなくてはなりません。
実体指定は`assert`以外に`match`でも活用されます。
```erg
arr: [Obj; _]
match! arr:
pair: [Obj; 2] => ...
ints: [Int; _] => ...
_ => ...
```
<p align='center'>
<a href='./13_algebraic.md'>Previous</a> | <a href='./15_quantified.md'>Next</a>
</p>

View file

@ -155,9 +155,7 @@ impl Runnable for DummyVM {
.to_string(),
);
if let Some(Expr::Def(def)) = last {
res.push_str(&format!(" ({}: ", def.sig.ident()));
res.push_str(&def.sig.t().to_string());
res.push(')');
res.push_str(&format!(" ({})", def.sig.ident()));
}
}
Ok(res)