Skip to content

Commit bee2e70

Browse files
committed
feat: set type variable
1 parent 8eb8cd7 commit bee2e70

File tree

10 files changed

+148
-3
lines changed

10 files changed

+148
-3
lines changed

crates/erg_compiler/context/compare.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,26 @@ impl Context {
418418
// ?T(<: Int) :> ?U(:> Int)
419419
// ?T(<: Nat) !:> ?U(:> Int) (if the upper bound of LHS is smaller than the lower bound of RHS, LHS cannot not be a supertype)
420420
// ?T(<: Nat) :> ?U(<: Int) (?U can be smaller than ?T)
421+
// ?T(:> ?U) :> ?U
422+
// ?U :> ?T(<: ?U)
423+
// ?T(: {Int, Str}) :> ?U(<: Int)
421424
(FreeVar(lfv), FreeVar(rfv)) => match (lfv.get_subsup(), rfv.get_subsup()) {
422425
(Some((_, l_sup)), Some((r_sub, _))) => self.supertype_of(&l_sup, &r_sub),
426+
(Some((l_sub, _)), None) if &l_sub == rhs => true,
427+
(None, Some((_, r_sup))) if lhs == &r_sup => true,
423428
_ => {
429+
let lfvt = lfv.get_type();
430+
// lfv: T: {Int, Str}, rhs: Int
431+
if let Some(tys) = lfvt.as_ref().and_then(|t| t.refinement_values()) {
432+
for tp in tys {
433+
let Ok(ty) = self.convert_tp_into_type(tp.clone()) else {
434+
continue;
435+
};
436+
if self.supertype_of(&ty, rhs) {
437+
return true;
438+
}
439+
}
440+
}
424441
if lfv.is_linked() {
425442
self.supertype_of(lfv.unsafe_crack(), rhs)
426443
} else if rfv.is_linked() {
@@ -504,6 +521,17 @@ impl Context {
504521
if let Some((_sub, sup)) = lfv.get_subsup() {
505522
lfv.do_avoiding_recursion_with(rhs, || self.supertype_of(&sup, rhs))
506523
} else if let Some(lfvt) = lfv.get_type() {
524+
// lfv: T: {Int, Str}, rhs: Int
525+
if let Some(tys) = lfvt.refinement_values() {
526+
for tp in tys {
527+
let Ok(ty) = self.convert_tp_into_type(tp.clone()) else {
528+
continue;
529+
};
530+
if self.supertype_of(&ty, rhs) {
531+
return true;
532+
}
533+
}
534+
}
507535
// e.g. lfv: ?L(: Int) is unreachable
508536
// but
509537
// ?L(: List(Type, 3)) :> List(Int, 3)

crates/erg_compiler/context/generalize.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,24 @@ impl<'c, 'q, 'l, L: Locational> Dereferencer<'c, 'q, 'l, L> {
857857
Ok(Type::FreeVar(fv))
858858
}
859859
}
860+
FreeVar(fv) if fv.get_type().is_some() => {
861+
let ty = fv.get_type().unwrap();
862+
if self.level <= fv.level().unwrap() {
863+
// T: {Int, Str} => Int or Str
864+
if let Some(tys) = ty.refinement_values() {
865+
let mut union = Never;
866+
for tp in tys {
867+
if let Ok(ty) = self.ctx.convert_tp_into_type(tp.clone()) {
868+
union = self.ctx.union(&union, &ty);
869+
}
870+
}
871+
return Ok(union);
872+
}
873+
Ok(Type::FreeVar(fv))
874+
} else {
875+
Ok(Type::FreeVar(fv))
876+
}
877+
}
860878
FreeVar(fv) if fv.is_unbound() => {
861879
if self.level == 0 {
862880
match &*fv.crack_constraint() {

crates/erg_compiler/context/instantiate_spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ impl Context {
183183
}
184184
_ => unreachable!(),
185185
};
186-
if constr.get_sub_sup().is_none() {
186+
if constr.get_type().is_some_and(|t| !t.is_meta_type()) {
187187
let tp = TyParam::named_free_var(lhs.inspect().clone(), self.level, constr);
188188
tv_cache.push_or_init_typaram(lhs, &tp, self)?;
189189
} else {

crates/erg_compiler/ty/mod.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,9 +1619,17 @@ impl CanbeFree for Type {
16191619
}
16201620

16211621
fn destructive_update_constraint(&self, new_constraint: Constraint, in_instantiation: bool) {
1622-
if let Some(fv) = self.as_free() {
1623-
fv.update_constraint(new_constraint, in_instantiation);
1622+
let Some(fv) = self.as_free() else {
1623+
return;
1624+
};
1625+
// self: T
1626+
// new_constraint: (:> T, <: U) => <: U
1627+
if new_constraint.get_sub_sup().is_some_and(|(sub, sup)| {
1628+
sub.contains_tvar_in_constraint(fv) || sup.contains_tvar_in_constraint(fv)
1629+
}) {
1630+
return;
16241631
}
1632+
fv.update_constraint(new_constraint, in_instantiation);
16251633
}
16261634
}
16271635

@@ -2731,6 +2739,28 @@ impl Type {
27312739
}
27322740
}
27332741

2742+
pub fn contains_tvar_in_constraint(&self, target: &FreeTyVar) -> bool {
2743+
match self {
2744+
Self::FreeVar(fv) if fv.is_linked() => fv.crack().contains_tvar_in_constraint(target),
2745+
Self::FreeVar(fv) if fv.constraint_is_typeof() => {
2746+
ref_addr_eq!(fv.forced_as_ref(), target.forced_as_ref())
2747+
}
2748+
Self::FreeVar(fv) => {
2749+
ref_addr_eq!(fv.forced_as_ref(), target.forced_as_ref())
2750+
|| fv
2751+
.get_subsup()
2752+
.map(|(sub, sup)| {
2753+
fv.do_avoiding_recursion(|| {
2754+
sub.contains_tvar_in_constraint(target)
2755+
|| sup.contains_tvar_in_constraint(target)
2756+
})
2757+
})
2758+
.unwrap_or(false)
2759+
}
2760+
_ => false,
2761+
}
2762+
}
2763+
27342764
pub fn contains_type(&self, target: &Type) -> bool {
27352765
if self == target {
27362766
// This operation can also be performed for recursive types
@@ -3095,6 +3125,14 @@ impl Type {
30953125
}
30963126
}
30973127

3128+
pub fn get_meta_type(&self) -> Option<Type> {
3129+
match self {
3130+
Self::FreeVar(fv) if fv.is_linked() => fv.crack().get_meta_type(),
3131+
Self::FreeVar(fv) if fv.is_unbound() => fv.get_type(),
3132+
_ => None,
3133+
}
3134+
}
3135+
30983136
pub const fn is_free_var(&self) -> bool {
30993137
matches!(self, Self::FreeVar(_))
31003138
}
@@ -3523,6 +3561,15 @@ impl Type {
35233561
!self.has_unbound_var()
35243562
}
35253563

3564+
pub fn is_meta_type(&self) -> bool {
3565+
match self {
3566+
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_meta_type(),
3567+
Self::Refinement(refine) => refine.t.is_meta_type(),
3568+
Self::ClassType | Self::TraitType | Self::Type => true,
3569+
_ => false,
3570+
}
3571+
}
3572+
35263573
pub fn typarams_len(&self) -> Option<usize> {
35273574
match self {
35283575
Self::FreeVar(fv) if fv.is_linked() => fv.crack().typarams_len(),

crates/erg_parser/ast.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,22 @@ impl Args {
552552
})
553553
}
554554
}
555+
556+
#[to_owned(cloned)]
557+
pub fn get_nth(&self, nth: usize) -> Option<&Expr> {
558+
self.pos_args.get(nth).map(|a| &a.expr)
559+
}
560+
561+
#[to_owned(cloned)]
562+
pub fn get_with_key(&self, key: &str) -> Option<&Expr> {
563+
self.kw_args.iter().find_map(|a| {
564+
if &a.keyword.content[..] == key {
565+
Some(&a.expr)
566+
} else {
567+
None
568+
}
569+
})
570+
}
555571
}
556572

557573
#[pyclass]

doc/EN/syntax/type/15_quantified.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ f|X, Y, Z| x: X, y: Y, z: Z =
8686
x + y + z + x
8787
```
8888

89+
```python
90+
# T is a subclass of Int or Str
91+
f|T: {Int, Str}|(x: T, _: T): T = x
92+
93+
_: Int = f 1, 2
94+
_: Str = f "a", "b"
95+
_ = f None, None # ERROR
96+
```
97+
8998
Unlike many languages with generics, all declared type variables must be used either in the temporary argument list (the `x: X, y: Y, z: Z` part) or in the arguments of other type variables.
9099
This is a requirement from Erg's language design that all type variables are inferrable from real arguments.
91100
So information that cannot be inferred, such as the return type, is passed from real arguments; Erg allows types to be passed from real arguments.

doc/JA/syntax/type/15_quantified.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ f|X, Y, Z| x: X, y: Y, z: Z =
8989
x + y + z + x
9090
```
9191

92+
```python
93+
# TはIntまたはStrのサブクラス
94+
f|T: {Int, Str}|(x: T, _: T): T = x
95+
96+
_: Int = f 1, 2
97+
_: Str = f "a", "b"
98+
_ = f None, None # ERROR
99+
```
100+
92101
ジェネリクスを持つ多くの言語と違い、宣言した型変数はすべて、仮引数リスト内(`x: X, y: Y, z: Z`の部分)か他の型変数の引数内かで使用されていなければなりません。
93102
これは、型変数はすべて実引数から推論可能であるというErgの言語設計からの要求です。
94103
なので、戻り値の型など推論ができない情報は、実引数から渡します。Ergは型を実引数から渡すことができるのです。

tests/should_err/set_type.er

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
f|T: {Int, Str}|(x: T, _: T): T = x
2+
3+
_ = f 1.1, 1.2 # ERR
4+
_ = f "a", None # ERR

tests/should_ok/set_type.er

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
f|T: {Int, Str}|(x: T, _: T): T = x
2+
3+
_ = f 1, 2
4+
_ = f "a", "b"

tests/test.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,11 @@ fn exec_self_type() -> Result<(), ()> {
397397
expect_success("tests/should_ok/self_type.er", 0)
398398
}
399399

400+
#[test]
401+
fn exec_set_type() -> Result<(), ()> {
402+
expect_success("tests/should_ok/set_type.er", 0)
403+
}
404+
400405
#[test]
401406
fn exec_slice() -> Result<(), ()> {
402407
expect_success("tests/should_ok/slice.er", 0)
@@ -638,6 +643,11 @@ fn exec_set() -> Result<(), ()> {
638643
expect_failure("examples/set.er", 3, 1)
639644
}
640645

646+
#[test]
647+
fn exec_set_type_err() -> Result<(), ()> {
648+
expect_failure("tests/should_err/set_type.er", 0, 3)
649+
}
650+
641651
#[test]
642652
fn exec_side_effect() -> Result<(), ()> {
643653
expect_failure("examples/side_effect.er", 5, 4)

0 commit comments

Comments
 (0)