Option type support #224

Merged
sb10q merged 8 commits from option into master 2022-03-26 15:09:15 +08:00
6 changed files with 126 additions and 99 deletions
Showing only changes of commit 762a7ccc14 - Show all commits

View File

@ -11,7 +11,7 @@ from embedding_map import EmbeddingMap
__all__ = [ __all__ = [
"Kernel", "KernelInvariant", "virtual", "Kernel", "KernelInvariant", "virtual",
"Option", "Some", "Option", "Some", "none",
"round64", "floor64", "ceil64", "round64", "floor64", "ceil64",
"extern", "kernel", "portable", "nac3", "extern", "kernel", "portable", "nac3",
"rpc", "ms", "us", "ns", "rpc", "ms", "us", "ns",
@ -50,19 +50,20 @@ class Option(Generic[T]):
def __repr__(self) -> str: def __repr__(self) -> str:
if self.is_none(): if self.is_none():
return "Option(None)" return "none"
ychenfo marked this conversation as resolved Outdated
Outdated
Review

Just none is fine.

Just ``none`` is fine.
else: else:
return "Some({})".format(repr(self._nac3_option)) return "Some({})".format(repr(self._nac3_option))
Outdated
Review

Python would report a stack overflow error already, and from its backtrace it should be clear enough what is happening.

Python would report a stack overflow error already, and from its backtrace it should be clear enough what is happening.
Outdated
Review

And by the way your proposal does not find all cases. For example, you could have option A containing option B, and option B containing option A, and it would overflow the stack and not print "Error(self recursion)".

Anyway those infinite recursions seem rare (you would have to try hard to shoot yourself in the foot by mutating the Option object) and produce a good enough backtrace without anything special.

You could put an underscore _nac3_option (see Python naming conventions) to highlight the fact that the user is not supposed to mutate the Option.

And by the way your proposal does not find all cases. For example, you could have option A containing option B, and option B containing option A, and it would overflow the stack and not print "Error(self recursion)". Anyway those infinite recursions seem rare (you would have to try hard to shoot yourself in the foot by mutating the ``Option`` object) and produce a good enough backtrace without anything special. You could put an underscore ``_nac3_option`` (see Python naming conventions) to highlight the fact that the user is not supposed to mutate the ``Option``.

Thanks for pointing this out! I have force pushed to update the __str__ and __repr__, remove the recursion check and use _nac3_option

Thanks for pointing this out! I have force pushed to update the `__str__` and `__repr__`, remove the recursion check and use `_nac3_option`
def __str__(self) -> str: def __str__(self) -> str:
if self.is_none(): if self.is_none():
return "None" return "none"
else: else:
return "Some({})".format(str(self._nac3_option)) return "Some({})".format(str(self._nac3_option))
def Some(v: T) -> Option[T]: def Some(v: T) -> Option[T]:
return Option(v) return Option(v)
none = Option(None)
def round64(x): def round64(x):
return round(x) return round(x)

View File

@ -353,7 +353,6 @@ impl Nac3 {
let builtins_mod = PyModule::import(py, "builtins").unwrap(); let builtins_mod = PyModule::import(py, "builtins").unwrap();
let id_fn = builtins_mod.getattr("id").unwrap(); let id_fn = builtins_mod.getattr("id").unwrap();
let type_fn = builtins_mod.getattr("type").unwrap();
let numpy_mod = PyModule::import(py, "numpy").unwrap(); let numpy_mod = PyModule::import(py, "numpy").unwrap();
let typing_mod = PyModule::import(py, "typing").unwrap(); let typing_mod = PyModule::import(py, "typing").unwrap();
let types_mod = PyModule::import(py, "types").unwrap(); let types_mod = PyModule::import(py, "types").unwrap();
@ -376,7 +375,13 @@ impl Nac3 {
get_attr_id(types_mod, "GenericAlias"), get_attr_id(types_mod, "GenericAlias"),
), ),
none: id_fn none: id_fn
.call1((type_fn.call1((builtins_mod.getattr("None").unwrap(),)).unwrap(),)) .call1((builtins_mod
.getattr("globals")
.unwrap()
.call0()
.unwrap()
.get_item("none")
.unwrap(),))
.unwrap() .unwrap()
.extract() .extract()
.unwrap(), .unwrap(),

View File

@ -282,24 +282,7 @@ impl InnerResolver {
} else if ty_id == self.primitive_ids.option { } else if ty_id == self.primitive_ids.option {
Ok(Ok((primitives.option, false))) Ok(Ok((primitives.option, false)))
} else if ty_id == self.primitive_ids.none { } else if ty_id == self.primitive_ids.none {
if let TypeEnum::TObj { params, .. } = unreachable!("none cannot be typeid")
unifier.get_ty_immutable(primitives.option).as_ref()
{
let var_map = params
.iter()
.map(|(id_var, ty)| {
if let TypeEnum::TVar { id, range, name, loc, .. } = &*unifier.get_ty(*ty) {
assert_eq!(*id, *id_var);
(*id, unifier.get_fresh_var_with_range(range, *name, *loc).0)
} else {
unreachable!()
}
})
.collect::<HashMap<_, _>>();
Ok(Ok((unifier.subst(primitives.option, &var_map).unwrap(), true)))
} else {
unreachable!("must be tobj")
}
} else if let Some(def_id) = self.pyid_to_def.read().get(&ty_id).cloned() { } else if let Some(def_id) = self.pyid_to_def.read().get(&ty_id).cloned() {
let def = defs[def_id.0].read(); let def = defs[def_id.0].read();
if let TopLevelDef::Class { object_id, type_vars, fields, methods, .. } = &*def { if let TopLevelDef::Class { object_id, type_vars, fields, methods, .. } = &*def {
@ -597,14 +580,32 @@ impl InnerResolver {
{ {
let field_data = match obj.getattr("_nac3_option") { let field_data = match obj.getattr("_nac3_option") {
Ok(d) => d, Ok(d) => d,
// None should be already handled above // we use `none = Option(None)`, so the obj always have attr `_nac3_option`
Err(_) => unreachable!("cannot be None") Err(_) => unreachable!("cannot be None")
}; };
let field_obj_id: u64 = self.helper.id_fn.call1(py, (field_data,))?.extract(py)?; // if is `none`
let zelf_obj_id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?; let zelf_id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?;
if field_obj_id == zelf_obj_id { if zelf_id == self.primitive_ids.none {
return Ok(Err("self recursive option type is not allowed".into())) if let TypeEnum::TObj { params, .. } =
unifier.get_ty_immutable(primitives.option).as_ref()
{
let var_map = params
.iter()
.map(|(id_var, ty)| {
if let TypeEnum::TVar { id, range, name, loc, .. } = &*unifier.get_ty(*ty) {
assert_eq!(*id, *id_var);
(*id, unifier.get_fresh_var_with_range(range, *name, *loc).0)
} else {
unreachable!()
} }
})
.collect::<HashMap<_, _>>();
return Ok(Ok(unifier.subst(primitives.option, &var_map).unwrap()))
} else {
unreachable!("must be tobj")
}
}
let ty = match self.get_obj_type(py, field_data, unifier, defs, primitives)? { let ty = match self.get_obj_type(py, field_data, unifier, defs, primitives)? {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
@ -845,6 +846,11 @@ impl InnerResolver {
global.set_initializer(&val); global.set_initializer(&val);
Ok(Some(global.as_pointer_value().into())) Ok(Some(global.as_pointer_value().into()))
} else if ty_id == self.primitive_ids.option { } else if ty_id == self.primitive_ids.option {
if id == self.primitive_ids.none {
// for option type, just a null ptr, whose type needs to be casted in codegen
// according to the type info attached in the ast
Ok(Some(ctx.ctx.i8_type().ptr_type(AddressSpace::Generic).const_null().into()))
} else {
match self match self
.get_obj_value(py, obj.getattr("_nac3_option").unwrap(), ctx, generator) .get_obj_value(py, obj.getattr("_nac3_option").unwrap(), ctx, generator)
.map_err(|e| { .map_err(|e| {
@ -871,10 +877,7 @@ impl InnerResolver {
}, },
None => Ok(None), None => Ok(None),
} }
} else if ty_id == self.primitive_ids.none { }
// for option type, just a null ptr, whose type needs to be casted in codegen
// according to the type info attached in the ast
Ok(Some(ctx.ctx.i8_type().ptr_type(AddressSpace::Generic).const_null().into()))
} else { } else {
let id_str = id.to_string(); let id_str = id.to_string();

View File

@ -200,22 +200,6 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
val val
} }
} }
Constant::None => {
match (
self.unifier.get_ty(ty).as_ref(),
self.unifier.get_ty(self.primitives.option).as_ref(),
) {
(
TypeEnum::TObj { obj_id, params, .. },
TypeEnum::TObj { obj_id: opt_id, .. },
) if *obj_id == *opt_id => self
.get_llvm_type(generator, *params.iter().next().unwrap().1)
.ptr_type(AddressSpace::Generic)
.const_null()
.into(),
_ => unreachable!("must be option type"),
}
}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -951,6 +935,22 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
let ty = expr.custom.unwrap(); let ty = expr.custom.unwrap();
ctx.gen_const(generator, value, ty).into() ctx.gen_const(generator, value, ty).into()
} }
ExprKind::Name { id, .. } if id == &"none".into() => {
match (
ctx.unifier.get_ty(expr.custom.unwrap()).as_ref(),
ctx.unifier.get_ty(ctx.primitives.option).as_ref(),
) {
(
TypeEnum::TObj { obj_id, params, .. },
TypeEnum::TObj { obj_id: opt_id, .. },
) if *obj_id == *opt_id => ctx
.get_llvm_type(generator, *params.iter().next().unwrap().1)
.ptr_type(AddressSpace::Generic)
.const_null()
.into(),
_ => unreachable!("must be option type"),
}
}
ExprKind::Name { id, .. } => match ctx.var_assignment.get(id) { ExprKind::Name { id, .. } => match ctx.var_assignment.get(id) {
Some((ptr, None, _)) => ctx.builder.build_load(*ptr, "load").into(), Some((ptr, None, _)) => ctx.builder.build_load(*ptr, "load").into(),
Some((_, Some(static_value), _)) => ValueEnum::Static(static_value.clone()), Some((_, Some(static_value), _)) => ValueEnum::Static(static_value.clone()),

View File

@ -20,6 +20,8 @@ impl<'a> Inferencer<'a> {
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<(), String> { ) -> Result<(), String> {
match &pattern.node { match &pattern.node {
ast::ExprKind::Name { id, .. } if id == &"none".into() =>
Err(format!("cannot assign to a `none` (at {})", pattern.location)),
ExprKind::Name { id, .. } => { ExprKind::Name { id, .. } => {
if !defined_identifiers.contains(id) { if !defined_identifiers.contains(id) {
defined_identifiers.insert(*id); defined_identifiers.insert(*id);
@ -70,6 +72,9 @@ impl<'a> Inferencer<'a> {
} }
match &expr.node { match &expr.node {
ExprKind::Name { id, .. } => { ExprKind::Name { id, .. } => {
if id == &"none".into() {
return Ok(());
}
self.should_have_value(expr)?; self.should_have_value(expr)?;
if !defined_identifiers.contains(id) { if !defined_identifiers.contains(id) {
match self.function_data.resolver.get_symbol_type( match self.function_data.resolver.get_symbol_type(

View File

@ -449,6 +449,27 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
Some(self.infer_constant(value, &expr.location)?) Some(self.infer_constant(value, &expr.location)?)
} }
ast::ExprKind::Name { id, .. } => { ast::ExprKind::Name { id, .. } => {
// the name `none` is special since it may have different types
if id == &"none".into() {
if let TypeEnum::TObj { params, .. } =
self.unifier.get_ty_immutable(self.primitives.option).as_ref()
{
let var_map = params
.iter()
.map(|(id_var, ty)| {
if let TypeEnum::TVar { id, range, name, loc, .. } = &*self.unifier.get_ty(*ty) {
assert_eq!(*id, *id_var);
(*id, self.unifier.get_fresh_var_with_range(range, *name, *loc).0)
} else {
unreachable!()
}
})
.collect::<HashMap<_, _>>();
Some(self.unifier.subst(self.primitives.option, &var_map).unwrap())
} else {
unreachable!("must be tobj")
}
} else {
if !self.defined_identifiers.contains(id) { if !self.defined_identifiers.contains(id) {
match self.function_data.resolver.get_symbol_type( match self.function_data.resolver.get_symbol_type(
self.unifier, self.unifier,
@ -469,6 +490,7 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
} }
Some(self.infer_identifier(*id)?) Some(self.infer_identifier(*id)?)
} }
}
ast::ExprKind::List { elts, .. } => Some(self.infer_list(elts)?), ast::ExprKind::List { elts, .. } => Some(self.infer_list(elts)?),
ast::ExprKind::Tuple { elts, .. } => Some(self.infer_tuple(elts)?), ast::ExprKind::Tuple { elts, .. } => Some(self.infer_tuple(elts)?),
ast::ExprKind::Attribute { value, attr, ctx } => { ast::ExprKind::Attribute { value, attr, ctx } => {
@ -933,17 +955,8 @@ impl<'a> Inferencer<'a> {
Ok(self.unifier.add_ty(TypeEnum::TTuple { ty: ty? })) Ok(self.unifier.add_ty(TypeEnum::TTuple { ty: ty? }))
} }
ast::Constant::Str(_) => Ok(self.primitives.str), ast::Constant::Str(_) => Ok(self.primitives.str),
ast::Constant::None => { ast::Constant::None
let option_ty = self.primitives.option; => report_error("CPython `None` not supported (nac3 uses `none` instead)", *loc),
let new_mapping = if let TypeEnum::TObj { params, .. } = &*self.unifier.get_ty_immutable(option_ty) {
let (id, _) = params.iter().next().unwrap();
// None can be Option[Any]
vec![(*id, self.unifier.get_fresh_var(None, None).0)].into_iter().collect()
} else {
unreachable!("option must be tobj")
};
Ok(self.unifier.subst(option_ty, &new_mapping).unwrap())
}
_ => report_error("not supported", *loc), _ => report_error("not supported", *loc),
} }
} }