Introduce 'Never' type as a safe alternative to always None options

Unfortunately, as documented in:

  https://github.com/IntersectMBO/cardano-ledger/issues/4571

  Some Option fields in the script context certificates are going to
  remain set to None, at least until the next Hard fork. There's a risk
  that people permanently lock their funds if they expect deposits on
  registration credentials to ever be `Some`.

  So, we introduce a special type that emulate an `Option` that can only
  ever be `None`. We call it `Never` and it is the first type of this
  kind (i.e. with constructors indexes not starting at 0).
This commit is contained in:
KtorZ
2024-08-27 11:06:02 +02:00
parent ff25fbd970
commit d74e36d0bc
13 changed files with 269 additions and 68 deletions

View File

@@ -400,6 +400,19 @@ impl TypedDataType {
doc: None,
}
}
pub fn is_never(&self) -> bool {
self.name == well_known::NEVER
&& self.constructors.len() == well_known::NEVER_CONSTRUCTORS.len()
&& self.location == Span::empty()
&& self
.constructors
.iter()
.zip(well_known::NEVER_CONSTRUCTORS)
.all(|(constructor, name)| {
name == &constructor.name && constructor.arguments.is_empty()
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]

View File

@@ -16,6 +16,8 @@ pub const LIST: &str = "List";
pub const MILLER_LOOP_RESULT: &str = "MillerLoopResult";
pub const OPTION: &str = "Option";
pub const OPTION_CONSTRUCTORS: &[&str] = &["Some", "None"];
pub const NEVER: &str = "Never";
pub const NEVER_CONSTRUCTORS: &[&str] = &["__hole", "Never"];
pub const ORDERING: &str = "Ordering";
pub const ORDERING_CONSTRUCTORS: &[&str] = &["Less", "Equal", "Greater"];
pub const PAIR: &str = "Pair";
@@ -297,6 +299,17 @@ impl Type {
})
}
pub fn never() -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
name: NEVER.to_string(),
module: "".to_string(),
args: vec![],
alias: None,
})
}
pub fn ordering() -> Rc<Type> {
Rc::new(Type::App {
public: true,

View File

@@ -168,6 +168,19 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
),
);
// Never
prelude.types.insert(
well_known::NEVER.to_string(),
TypeConstructor::primitive(Type::never()),
);
prelude.types_constructors.insert(
well_known::NEVER.to_string(),
ValueConstructor::known_adt(
&mut prelude.values,
&[(well_known::NEVER_CONSTRUCTORS[1], Type::never())],
),
);
// Cardano ScriptContext
prelude.types.insert(
well_known::SCRIPT_CONTEXT.to_string(),
@@ -1503,6 +1516,15 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap<DataTypeKey, TypedDa
option_data_type,
);
// Never
data_types.insert(
DataTypeKey {
module_name: "".to_string(),
defined_type: well_known::NEVER.to_string(),
},
TypedDataType::never(),
);
// PRNG
let prng_data_type = TypedDataType::prng();
data_types.insert(
@@ -1609,4 +1631,8 @@ impl TypedDataType {
typed_parameters: vec![tipo],
}
}
pub fn never() -> Self {
DataType::known_enum(well_known::NEVER, well_known::NEVER_CONSTRUCTORS)
}
}

View File

@@ -1434,7 +1434,9 @@ impl<'a> CodeGenerator<'a> {
.unwrap_or_else(|| unreachable!("Failed to find definition for {}", name));
let then = if props.kind.is_expect()
&& (data_type.constructors.len() > 1 || props.full_check)
&& (data_type.constructors.len() > 1
|| props.full_check
|| data_type.is_never())
{
let (index, _) = data_type
.constructors
@@ -1459,8 +1461,8 @@ impl<'a> CodeGenerator<'a> {
)
} else {
assert!(
data_type.constructors.len() == 1,
"attempted expect on a type with more or less than 1 constructor: \nis_expect? {}\nfull_check? {}\ndata_type={data_type:#?}\n{}",
data_type.constructors.len() == 1 || data_type.is_never(),
"attempted let-assignment on a type with more or less than 1 constructor: \nis_expect? {}\nfull_check? {}\ndata_type={data_type:#?}\n{}",
props.kind.is_expect(),
props.full_check,
name,
@@ -1978,9 +1980,20 @@ impl<'a> CodeGenerator<'a> {
let otherwise_delayed = AirTree::local_var("otherwise_delayed", Type::void());
let is_never = data_type.is_never();
let constr_clauses = data_type.constructors.iter().enumerate().rfold(
otherwise_delayed.clone(),
|acc, (index, constr)| {
// NOTE: For the Never type, we have an placeholder first constructor
// that must be ignored. The Never type is considered to have only one
// constructor starting at index 1 so it shouldn't be possible to
// cast from Data into the first constructor. There's virtually no
// constructor at index 0.
if is_never && index == 0 {
return acc;
}
let mut constr_args = vec![];
let constr_then = constr.arguments.iter().enumerate().rfold(
@@ -2169,7 +2182,7 @@ impl<'a> CodeGenerator<'a> {
),
)
} else if let Some(data_type) = data_type {
if data_type.constructors.len() > 1 {
if data_type.constructors.len() > 1 && !data_type.is_never() {
AirTree::clause(
&props.original_subject_name,
clause_cond,
@@ -5584,8 +5597,18 @@ impl<'a> CodeGenerator<'a> {
} => {
let tail_name_prefix = "__tail_index";
let data_type = lookup_data_type_by_tipo(&self.data_types, &tipo)
.unwrap_or_else(|| panic!("HOW DID YOU DO THIS ON BOOL OR VOID"));
let data_type =
lookup_data_type_by_tipo(&self.data_types, &tipo).unwrap_or_else(|| {
panic!(
"Attempted record update on an unknown type!\ntype: {:#?}",
tipo
)
});
assert!(
!data_type.is_never(),
"Attempted record update on a Never type.",
);
let constructor_field_count = data_type.constructors[0].arguments.len();
let record = arg_stack.pop().unwrap();