mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-27 19:59:07 +00:00
feat: add as
operator
This commit is contained in:
parent
1c6a6b2ec8
commit
daf01f3cf2
14 changed files with 119 additions and 12 deletions
|
@ -1452,6 +1452,11 @@ impl PyCodeGenerator {
|
|||
self.emit_push_null();
|
||||
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
|
||||
|
|
|
@ -79,6 +79,7 @@ fn op_to_name(op: OpKind) -> &'static str {
|
|||
OpKind::Le => "__le__",
|
||||
OpKind::Gt => "__gt__",
|
||||
OpKind::Ge => "__ge__",
|
||||
OpKind::As => "__as__",
|
||||
OpKind::And => "__and__",
|
||||
OpKind::Or => "__or__",
|
||||
OpKind::Not => "__not__",
|
||||
|
|
|
@ -604,6 +604,10 @@ impl Context {
|
|||
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_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 */
|
||||
// TODO: +/- Bool would like to be warned
|
||||
let M = mono_q(TY_M, subtypeof(mono(MUTIZABLE)));
|
||||
|
|
|
@ -336,6 +336,7 @@ const OP_DIV: &str = "__div__";
|
|||
const OP_FLOOR_DIV: &str = "__floordiv__";
|
||||
const OP_ABS: &str = "__abs__";
|
||||
const OP_PARTIAL_CMP: &str = "__partial_cmp__";
|
||||
const OP_AS: &str = "__as__";
|
||||
const OP_AND: &str = "__and__";
|
||||
const OP_OR: &str = "__or__";
|
||||
const OP_POW: &str = "__pow__";
|
||||
|
@ -395,6 +396,8 @@ const TY_L: &str = "L";
|
|||
const TY_N: &str = "N";
|
||||
const TY_M: &str = "M";
|
||||
const TY_O: &str = "O";
|
||||
const TY_SUB: &str = "Sub";
|
||||
const TY_SUP: &str = "Sup";
|
||||
|
||||
const KW_OLD: &str = "old";
|
||||
const KW_B: &str = "b";
|
||||
|
|
|
@ -94,6 +94,7 @@ pub fn binop_to_dname(op: &str) -> &str {
|
|||
"&&" | "&" | "and" => "__and__",
|
||||
"||" | "|" | "or" => "__or__",
|
||||
"^^" | "^" => "__xor__",
|
||||
"as" => "__as__",
|
||||
"in" => "__in__",
|
||||
"notin" => "__notin__", // NOTE: this doesn't exist in Python
|
||||
"contains" => "__contains__",
|
||||
|
@ -138,6 +139,7 @@ pub fn readable_name(name: &str) -> &str {
|
|||
"__lorng__" => "`<..`",
|
||||
"__rorng__" => "`..<`",
|
||||
"__orng__" => "`<..<`",
|
||||
"__as__" => "`as`",
|
||||
"__and__" => "`and`", // TODO: `&&` if not boolean
|
||||
"__or__" => "`or`", // TODO: `||` if not boolean
|
||||
"__in__" => "`in`",
|
||||
|
|
|
@ -482,6 +482,11 @@ pub fn subtypeof(sup: Type) -> Constraint {
|
|||
Constraint::new_sandwiched(Type::Never, sup)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn supertypeof(sub: Type) -> Constraint {
|
||||
Constraint::new_sandwiched(sub, Type::Obj)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mono_q_tp<S: Into<Str>>(name: S, constr: Constraint) -> TyParam {
|
||||
TyParam::mono_q(name, constr)
|
||||
|
|
|
@ -39,6 +39,7 @@ pub enum OpKind {
|
|||
Le,
|
||||
Eq,
|
||||
Ne,
|
||||
As,
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
|
@ -69,6 +70,7 @@ impl fmt::Display for OpKind {
|
|||
Self::Le => write!(f, "<="),
|
||||
Self::Eq => write!(f, "=="),
|
||||
Self::Ne => write!(f, "!="),
|
||||
Self::As => write!(f, "as"),
|
||||
Self::And => write!(f, "and"),
|
||||
Self::Or => write!(f, "or"),
|
||||
Self::Not => write!(f, "not"),
|
||||
|
|
|
@ -700,6 +700,7 @@ impl Lexer /*<'a>*/ {
|
|||
// e.g. and(true, true, true) = true
|
||||
let kind = match &cont[..] {
|
||||
"and" => AndOp,
|
||||
"as" => AsOp,
|
||||
"or" => OrOp,
|
||||
"in" => InOp,
|
||||
"notin" => NotInOp,
|
||||
|
|
|
@ -108,6 +108,8 @@ pub enum TokenKind {
|
|||
IsNotOp,
|
||||
/// `and`
|
||||
AndOp,
|
||||
/// `as`
|
||||
AsOp,
|
||||
/// `or`
|
||||
OrOp,
|
||||
/// `dot` (scalar product)
|
||||
|
@ -269,7 +271,8 @@ impl TokenKind {
|
|||
BitXor => 130, // ^^
|
||||
BitOr => 120, // ||
|
||||
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
|
||||
| IsNotOp => 90, // < > <= >= == != as in notin is isnot
|
||||
AndOp => 80, // and
|
||||
OrOp => 70, // or
|
||||
FuncArrow | ProcArrow | Inclusion => 60, // -> => <-
|
||||
|
|
|
@ -58,6 +58,39 @@ assert Ratio.from(1) == 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
|
||||
|
||||
Since downcasting is generally unsafe and the conversion method is non-trivial, we instead implement ``TryFrom.try_from``.
|
||||
|
|
|
@ -39,11 +39,12 @@ assert f 1 == 2
|
|||
関数は同じ引数に対して同じ値を返すべきですが、その前提が破れてしまっています。
|
||||
`i`は呼び出し時に初めて評価されることに注意してください。
|
||||
|
||||
関数定義時点での可変オブジェクトの内容がほしい場合は`.clone`を呼び出します。
|
||||
関数定義時点での可変オブジェクトの「中身」がほしい場合は`.freeze`を呼び出します。
|
||||
このメソッドは`Int! -> Int`のように不変化可能な型で使えるメソッドで、不変化したオブジェクトを返します。
|
||||
|
||||
```python
|
||||
i = !0
|
||||
immut_i = i.clone().freeze()
|
||||
immut_i = i.freeze()
|
||||
f x = immut_i + x
|
||||
assert f 1 == 1
|
||||
i.add! 1
|
||||
|
|
|
@ -60,6 +60,41 @@ assert Ratio.from(1) == 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`の実装で実現します。
|
||||
|
|
7
tests/should_err/as.er
Normal file
7
tests/should_err/as.er
Normal file
|
@ -0,0 +1,7 @@
|
|||
_ = 1 as Str # ERR
|
||||
_ = 1 as Never # ERR
|
||||
|
||||
n = 1
|
||||
_ = n.times!
|
||||
i = n as Int
|
||||
_ = i.times! # ERR
|
|
@ -237,6 +237,11 @@ fn exec_array_err() -> Result<(), ()> {
|
|||
expect_failure("examples/array.er", 0, 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_as() -> Result<(), ()> {
|
||||
expect_failure("tests/should_err/as.er", 0, 3)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_assert_cast() -> Result<(), ()> {
|
||||
expect_failure("examples/assert_cast.er", 0, 3)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue