feat: add as operator

This commit is contained in:
Shunsuke Shibayama 2023-04-19 15:20:46 +09:00
parent 1c6a6b2ec8
commit daf01f3cf2
14 changed files with 119 additions and 12 deletions

View file

@ -1452,6 +1452,11 @@ impl PyCodeGenerator {
self.emit_push_null(); self.emit_push_null();
self.emit_load_name_instr(Identifier::private("#in_operator")); self.emit_load_name_instr(Identifier::private("#in_operator"));
} }
// (x as T) == x
TokenKind::AsOp => {
self.emit_expr(*bin.lhs);
return;
}
_ => {} _ => {}
} }
let lhs_t = bin let lhs_t = bin

View file

@ -79,6 +79,7 @@ fn op_to_name(op: OpKind) -> &'static str {
OpKind::Le => "__le__", OpKind::Le => "__le__",
OpKind::Gt => "__gt__", OpKind::Gt => "__gt__",
OpKind::Ge => "__ge__", OpKind::Ge => "__ge__",
OpKind::As => "__as__",
OpKind::And => "__and__", OpKind::And => "__and__",
OpKind::Or => "__or__", OpKind::Or => "__or__",
OpKind::Not => "__not__", OpKind::Not => "__not__",

View file

@ -604,6 +604,10 @@ impl Context {
let op_t = bin_op(I, T, Bool).quantify(); let op_t = bin_op(I, T, Bool).quantify();
self.register_builtin_erg_impl(OP_IN, op_t.clone(), Const, Visibility::BUILTIN_PRIVATE); self.register_builtin_erg_impl(OP_IN, op_t.clone(), Const, Visibility::BUILTIN_PRIVATE);
self.register_builtin_erg_impl(OP_NOT_IN, op_t, Const, Visibility::BUILTIN_PRIVATE); self.register_builtin_erg_impl(OP_NOT_IN, op_t, Const, Visibility::BUILTIN_PRIVATE);
let Sub = mono_q(TY_SUB, instanceof(Type));
let Sup = mono_q(TY_SUP, supertypeof(Sub.clone()));
let op_t = bin_op(Sub, tp_enum(Type, set! { ty_tp(Sup.clone()) }), Sup).quantify();
self.register_builtin_erg_impl(OP_AS, op_t, Const, Visibility::BUILTIN_PRIVATE);
/* unary */ /* unary */
// TODO: +/- Bool would like to be warned // TODO: +/- Bool would like to be warned
let M = mono_q(TY_M, subtypeof(mono(MUTIZABLE))); let M = mono_q(TY_M, subtypeof(mono(MUTIZABLE)));

View file

@ -336,6 +336,7 @@ const OP_DIV: &str = "__div__";
const OP_FLOOR_DIV: &str = "__floordiv__"; const OP_FLOOR_DIV: &str = "__floordiv__";
const OP_ABS: &str = "__abs__"; const OP_ABS: &str = "__abs__";
const OP_PARTIAL_CMP: &str = "__partial_cmp__"; const OP_PARTIAL_CMP: &str = "__partial_cmp__";
const OP_AS: &str = "__as__";
const OP_AND: &str = "__and__"; const OP_AND: &str = "__and__";
const OP_OR: &str = "__or__"; const OP_OR: &str = "__or__";
const OP_POW: &str = "__pow__"; const OP_POW: &str = "__pow__";
@ -395,6 +396,8 @@ const TY_L: &str = "L";
const TY_N: &str = "N"; const TY_N: &str = "N";
const TY_M: &str = "M"; const TY_M: &str = "M";
const TY_O: &str = "O"; const TY_O: &str = "O";
const TY_SUB: &str = "Sub";
const TY_SUP: &str = "Sup";
const KW_OLD: &str = "old"; const KW_OLD: &str = "old";
const KW_B: &str = "b"; const KW_B: &str = "b";

View file

@ -94,6 +94,7 @@ pub fn binop_to_dname(op: &str) -> &str {
"&&" | "&" | "and" => "__and__", "&&" | "&" | "and" => "__and__",
"||" | "|" | "or" => "__or__", "||" | "|" | "or" => "__or__",
"^^" | "^" => "__xor__", "^^" | "^" => "__xor__",
"as" => "__as__",
"in" => "__in__", "in" => "__in__",
"notin" => "__notin__", // NOTE: this doesn't exist in Python "notin" => "__notin__", // NOTE: this doesn't exist in Python
"contains" => "__contains__", "contains" => "__contains__",
@ -138,6 +139,7 @@ pub fn readable_name(name: &str) -> &str {
"__lorng__" => "`<..`", "__lorng__" => "`<..`",
"__rorng__" => "`..<`", "__rorng__" => "`..<`",
"__orng__" => "`<..<`", "__orng__" => "`<..<`",
"__as__" => "`as`",
"__and__" => "`and`", // TODO: `&&` if not boolean "__and__" => "`and`", // TODO: `&&` if not boolean
"__or__" => "`or`", // TODO: `||` if not boolean "__or__" => "`or`", // TODO: `||` if not boolean
"__in__" => "`in`", "__in__" => "`in`",

View file

@ -482,6 +482,11 @@ pub fn subtypeof(sup: Type) -> Constraint {
Constraint::new_sandwiched(Type::Never, sup) Constraint::new_sandwiched(Type::Never, sup)
} }
#[inline]
pub fn supertypeof(sub: Type) -> Constraint {
Constraint::new_sandwiched(sub, Type::Obj)
}
#[inline] #[inline]
pub fn mono_q_tp<S: Into<Str>>(name: S, constr: Constraint) -> TyParam { pub fn mono_q_tp<S: Into<Str>>(name: S, constr: Constraint) -> TyParam {
TyParam::mono_q(name, constr) TyParam::mono_q(name, constr)

View file

@ -39,6 +39,7 @@ pub enum OpKind {
Le, Le,
Eq, Eq,
Ne, Ne,
As,
And, And,
Or, Or,
Not, Not,
@ -69,6 +70,7 @@ impl fmt::Display for OpKind {
Self::Le => write!(f, "<="), Self::Le => write!(f, "<="),
Self::Eq => write!(f, "=="), Self::Eq => write!(f, "=="),
Self::Ne => write!(f, "!="), Self::Ne => write!(f, "!="),
Self::As => write!(f, "as"),
Self::And => write!(f, "and"), Self::And => write!(f, "and"),
Self::Or => write!(f, "or"), Self::Or => write!(f, "or"),
Self::Not => write!(f, "not"), Self::Not => write!(f, "not"),

View file

@ -700,6 +700,7 @@ impl Lexer /*<'a>*/ {
// e.g. and(true, true, true) = true // e.g. and(true, true, true) = true
let kind = match &cont[..] { let kind = match &cont[..] {
"and" => AndOp, "and" => AndOp,
"as" => AsOp,
"or" => OrOp, "or" => OrOp,
"in" => InOp, "in" => InOp,
"notin" => NotInOp, "notin" => NotInOp,

View file

@ -108,6 +108,8 @@ pub enum TokenKind {
IsNotOp, IsNotOp,
/// `and` /// `and`
AndOp, AndOp,
/// `as`
AsOp,
/// `or` /// `or`
OrOp, OrOp,
/// `dot` (scalar product) /// `dot` (scalar product)
@ -269,15 +271,16 @@ impl TokenKind {
BitXor => 130, // ^^ BitXor => 130, // ^^
BitOr => 120, // || BitOr => 120, // ||
Closed | LeftOpen | RightOpen | Open => 100, // range operators Closed | LeftOpen | RightOpen | Open => 100, // range operators
Less | Gre | LessEq | GreEq | DblEq | NotEq | InOp | NotInOp | IsOp | IsNotOp => 90, // < > <= >= == != in notin is isnot Less | Gre | LessEq | GreEq | DblEq | NotEq | AsOp | InOp | NotInOp | IsOp
AndOp => 80, // and | IsNotOp => 90, // < > <= >= == != as in notin is isnot
OrOp => 70, // or AndOp => 80, // and
FuncArrow | ProcArrow | Inclusion => 60, // -> => <- OrOp => 70, // or
Colon | SupertypeOf | SubtypeOf => 50, // : :> <: FuncArrow | ProcArrow | Inclusion => 60, // -> => <-
Comma => 40, // , Colon | SupertypeOf | SubtypeOf => 50, // : :> <:
Assign | Walrus => 20, // = := Comma => 40, // ,
Newline | Semi => 10, // \n ; Assign | Walrus => 20, // = :=
LParen | LBrace | LSqBr | Indent => 0, // ( { [ Indent Newline | Semi => 10, // \n ;
LParen | LBrace | LSqBr | Indent => 0, // ( { [ Indent
_ => return None, _ => return None,
}; };
Some(prec) Some(prec)

View file

@ -58,6 +58,39 @@ assert Ratio.from(1) == 1.0
assert 1.into<Ratio>() == 1.0 assert 1.into<Ratio>() == 1.0
``` ```
## Forced upcasting
In many cases, upcasting of objects is automatic, depending on the function or operator that is called.
However, there are cases when you want to force upcasting. In that case, you can use `as`.
```python,compile_fail
n = 1
n.times! do: print!
print! "Hello"
i = n as Int
i.times! do: # ERR
"Hello"
s = n as Str # ERR
```
You cannot cast to unrelated types or subtypes with ``as``.
## Forced casting
You can use `typing.cast` to force casting. This can convert the target to any type.
In Python, `typing.cast` does nothing at runtime, but in Erg the conversion will be performed by the constructor. This is to protect type safety.
```python
typing = pyimport "typing"
s = typing.cast Str, 1
assert s == "1"
print! s + "a" # 1a
```
## Downcasting ## Downcasting
Since downcasting is generally unsafe and the conversion method is non-trivial, we instead implement ``TryFrom.try_from``. Since downcasting is generally unsafe and the conversion method is non-trivial, we instead implement ``TryFrom.try_from``.
@ -73,4 +106,4 @@ IntTryFromFloat.
<p align='center'> <p align='center'>
<a href='./16_subtyping.md'>Previous</a> | <a href='./18_mut.md'>Next</a> <a href='./16_subtyping.md'>Previous</a> | <a href='./18_mut.md'>Next</a>
</p> </p>

View file

@ -39,11 +39,12 @@ assert f 1 == 2
関数は同じ引数に対して同じ値を返すべきですが、その前提が破れてしまっています。 関数は同じ引数に対して同じ値を返すべきですが、その前提が破れてしまっています。
`i`は呼び出し時に初めて評価されることに注意してください。 `i`は呼び出し時に初めて評価されることに注意してください。
関数定義時点での可変オブジェクトの内容がほしい場合は`.clone`を呼び出します。 関数定義時点での可変オブジェクトの「中身」がほしい場合は`.freeze`を呼び出します。
このメソッドは`Int! -> Int`のように不変化可能な型で使えるメソッドで、不変化したオブジェクトを返します。
```python ```python
i = !0 i = !0
immut_i = i.clone().freeze() immut_i = i.freeze()
f x = immut_i + x f x = immut_i + x
assert f 1 == 1 assert f 1 == 1
i.add! 1 i.add! 1

View file

@ -60,6 +60,41 @@ assert Ratio.from(1) == 1.0
assert 1.into(Ratio) == 1.0 assert 1.into(Ratio) == 1.0
``` ```
## 強制アップキャスト
多くの場合、オブジェクトのアップキャストは呼び出す関数や演算子に応じて自動で行われます。
しかし、アップキャストを強制したい場合もあります。その場合は`as`を使います。
```python,compile_fail
n = 1
n.times! do:
print! "Hello"
i = n as Int
i.times! do: # ERR
print! "Hello"
s = n as Str # ERR
```
`as`では関係のない型や、部分型にキャストすることはできません。
## 強制キャスト
`typing.cast`を使って、型を強制的にキャストすることができます。
これは対象をどんな型にでも変換出来ます。
Pythonの`typing.cast`はランタイムに何も行わない関数ですが、Ergではコンストラクタによる変換が入ります。
これは型安全性を保護するためです。
```python
typing = pyimport "typing"
s = typing.cast Str, 1
assert s == "1"
print! s + "a" # 1a
```
## ダウンキャスト ## ダウンキャスト
ダウンキャストは一般に安全ではなく、変換方法も自明ではないため、代わりに`TryFrom.try_from`の実装で実現します。 ダウンキャストは一般に安全ではなく、変換方法も自明ではないため、代わりに`TryFrom.try_from`の実装で実現します。

7
tests/should_err/as.er Normal file
View file

@ -0,0 +1,7 @@
_ = 1 as Str # ERR
_ = 1 as Never # ERR
n = 1
_ = n.times!
i = n as Int
_ = i.times! # ERR

View file

@ -237,6 +237,11 @@ fn exec_array_err() -> Result<(), ()> {
expect_failure("examples/array.er", 0, 1) expect_failure("examples/array.er", 0, 1)
} }
#[test]
fn exec_as() -> Result<(), ()> {
expect_failure("tests/should_err/as.er", 0, 3)
}
#[test] #[test]
fn exec_assert_cast() -> Result<(), ()> { fn exec_assert_cast() -> Result<(), ()> {
expect_failure("examples/assert_cast.er", 0, 3) expect_failure("examples/assert_cast.er", 0, 3)