Skip to content

Commit

Permalink
Implements the Never ! types as a TypeInfo bottom type. (FuelLabs#5602
Browse files Browse the repository at this point in the history
)

## Description

We now parse the `!` as a TypeInfo::Never, and remove the usage of empty
enums as Never type in our code.

This commit removes completely the DeterministicallyAborts and
TypeCheckUnificationContext.

The DeterministicallyAborts can be removed because the Never TypeInfo is
now propagated using the type checker. Code blocks that return, break,
continue, or call an expression that returns Never, are marked as Never.

Partially fixes FuelLabs#5562.

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
  • Loading branch information
esdrubal authored Feb 20, 2024
1 parent 08b102d commit b32d0e0
Show file tree
Hide file tree
Showing 42 changed files with 276 additions and 410 deletions.
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
- [Associated Types](./advanced/associated_types.md)
- [Generics and Trait Constraints](./advanced/generics_and_trait_constraints.md)
- [Assembly](./advanced/assembly.md)
- [Never Type](./advanced/never_type.md)
- [Common Collections](./common-collections/index.md)
- [Vectors on the Heap](./common-collections/vec.md)
- [Storage Vectors](./common-collections/storage_vec.md)
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/advanced/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Advanced concepts.
- [Associated Types](./associated_types.md)
- [Generics and Trait Constraints](./generics_and_trait_constraints.md)
- [Assembly](./assembly.md)
- [Never Type](./never_type.md)
53 changes: 53 additions & 0 deletions docs/book/src/advanced/never_type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Never Type

The Never type `!` represents the type of computations which never resolve to any value at all.

## Additional Information

`break`, `continue` and `return` expressions also have type `!`. For example we are allowed to
write:

```sway
let x: ! = {
return 123
};
```

Although the `let` is pointless here, it illustrates the meaning of `!`. Since `x` is never
assigned a value (because `return` returns from the entire function), `x` can be given type
`Never`. We could also replace `return 123` with a `revert()` or a never-ending `loop` and this code
would still be valid.

A more realistic usage of `Never` is in this code:

```sway
let num: u32 = match get_a_number() {
Some(num) => num,
None => break,
};
```

Both match arms must produce values of type [`u32`], but since `break` never produces a value
at all we know it can never produce a value which isn't a [`u32`]. This illustrates another
behaviour of the `!` type - expressions with type `!` will coerce into any other type.

Note that `!` type coerces into any other type, another example of this would be:

```sway
let x: u32 = {
return 123
};
```

Regardless of the type of `x`, the return block of type `Never` will always coerce into `x` type.

## Examples

```sway
fn foo() {
let num: u64 = match Option::None::<u64> {
Some(num) => num,
None => return,
};
}
```
4 changes: 1 addition & 3 deletions forc-plugins/forc-doc/src/tests/expects/impl_trait/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn test_impl_traits_default() {
assert_search_js(
&doc_path,
&expect![[r#"
var SEARCH_INDEX={"core":[{"html_filename":"trait.AsRawSlice.html","module_info":["core","raw_slice"],"name":"AsRawSlice","preview":"Trait to return a type as a <code>raw_slice</code>.\n","type_name":"trait"},{"html_filename":"fn.from_str_array.html","module_info":["core","str"],"name":"from_str_array","preview":"","type_name":"function"},{"html_filename":"trait.Add.html","module_info":["core","ops"],"name":"Add","preview":"Trait for the addition of two values.\n","type_name":"trait"},{"html_filename":"trait.Subtract.html","module_info":["core","ops"],"name":"Subtract","preview":"Trait for the subtraction of two values.\n","type_name":"trait"},{"html_filename":"trait.Multiply.html","module_info":["core","ops"],"name":"Multiply","preview":"Trait for the multiplication of two values.\n","type_name":"trait"},{"html_filename":"trait.Divide.html","module_info":["core","ops"],"name":"Divide","preview":"Trait for the division of two values.\n","type_name":"trait"},{"html_filename":"trait.Mod.html","module_info":["core","ops"],"name":"Mod","preview":"Trait for the modulo of two values.\n","type_name":"trait"},{"html_filename":"trait.Not.html","module_info":["core","ops"],"name":"Not","preview":"Trait to invert a type.\n","type_name":"trait"},{"html_filename":"trait.Eq.html","module_info":["core","ops"],"name":"Eq","preview":"Trait to evaluate if two types are equal.\n","type_name":"trait"},{"html_filename":"trait.Ord.html","module_info":["core","ops"],"name":"Ord","preview":"Trait to evaluate if one value is greater or less than another of the same type.\n","type_name":"trait"},{"html_filename":"trait.BitwiseAnd.html","module_info":["core","ops"],"name":"BitwiseAnd","preview":"Trait to bitwise AND two values of the same type.\n","type_name":"trait"},{"html_filename":"trait.BitwiseOr.html","module_info":["core","ops"],"name":"BitwiseOr","preview":"Trait to bitwise OR two values of the same type.\n","type_name":"trait"},{"html_filename":"trait.BitwiseXor.html","module_info":["core","ops"],"name":"BitwiseXor","preview":"Trait to bitwise XOR two values of the same type.\n","type_name":"trait"},{"html_filename":"trait.Shift.html","module_info":["core","ops"],"name":"Shift","preview":"Trait to bit shift a value.\n","type_name":"trait"},{"html_filename":"enum.Never.html","module_info":["core","never"],"name":"Never","preview":"<code>Never</code> represents the type of computations which never resolve to any value at all.","type_name":"enum"},{"html_filename":"struct.StorageKey.html","module_info":["core","storage"],"name":"StorageKey","preview":"Describes a location in storage.\n","type_name":"struct"},{"html_filename":"struct.Buffer.html","module_info":["core","codec"],"name":"Buffer","preview":"","type_name":"struct"},{"html_filename":"trait.AbiEncode.html","module_info":["core","codec"],"name":"AbiEncode","preview":"","type_name":"trait"},{"html_filename":"fn.encode.html","module_info":["core","codec"],"name":"encode","preview":"","type_name":"function"}],"impl_traits":[{"html_filename":"trait.Foo.html","module_info":["impl_traits","foo"],"name":"Foo","preview":"","type_name":"trait"},{"html_filename":"trait.Baz.html","module_info":["impl_traits","foo"],"name":"Baz","preview":"","type_name":"trait"},{"html_filename":"struct.Bar.html","module_info":["impl_traits","bar"],"name":"Bar","preview":"","type_name":"struct"}]};
var SEARCH_INDEX={"core":[{"html_filename":"trait.AsRawSlice.html","module_info":["core","raw_slice"],"name":"AsRawSlice","preview":"Trait to return a type as a <code>raw_slice</code>.\n","type_name":"trait"},{"html_filename":"fn.from_str_array.html","module_info":["core","str"],"name":"from_str_array","preview":"","type_name":"function"},{"html_filename":"trait.Add.html","module_info":["core","ops"],"name":"Add","preview":"Trait for the addition of two values.\n","type_name":"trait"},{"html_filename":"trait.Subtract.html","module_info":["core","ops"],"name":"Subtract","preview":"Trait for the subtraction of two values.\n","type_name":"trait"},{"html_filename":"trait.Multiply.html","module_info":["core","ops"],"name":"Multiply","preview":"Trait for the multiplication of two values.\n","type_name":"trait"},{"html_filename":"trait.Divide.html","module_info":["core","ops"],"name":"Divide","preview":"Trait for the division of two values.\n","type_name":"trait"},{"html_filename":"trait.Mod.html","module_info":["core","ops"],"name":"Mod","preview":"Trait for the modulo of two values.\n","type_name":"trait"},{"html_filename":"trait.Not.html","module_info":["core","ops"],"name":"Not","preview":"Trait to invert a type.\n","type_name":"trait"},{"html_filename":"trait.Eq.html","module_info":["core","ops"],"name":"Eq","preview":"Trait to evaluate if two types are equal.\n","type_name":"trait"},{"html_filename":"trait.Ord.html","module_info":["core","ops"],"name":"Ord","preview":"Trait to evaluate if one value is greater or less than another of the same type.\n","type_name":"trait"},{"html_filename":"trait.BitwiseAnd.html","module_info":["core","ops"],"name":"BitwiseAnd","preview":"Trait to bitwise AND two values of the same type.\n","type_name":"trait"},{"html_filename":"trait.BitwiseOr.html","module_info":["core","ops"],"name":"BitwiseOr","preview":"Trait to bitwise OR two values of the same type.\n","type_name":"trait"},{"html_filename":"trait.BitwiseXor.html","module_info":["core","ops"],"name":"BitwiseXor","preview":"Trait to bitwise XOR two values of the same type.\n","type_name":"trait"},{"html_filename":"trait.Shift.html","module_info":["core","ops"],"name":"Shift","preview":"Trait to bit shift a value.\n","type_name":"trait"},{"html_filename":"struct.StorageKey.html","module_info":["core","storage"],"name":"StorageKey","preview":"Describes a location in storage.\n","type_name":"struct"},{"html_filename":"struct.Buffer.html","module_info":["core","codec"],"name":"Buffer","preview":"","type_name":"struct"},{"html_filename":"trait.AbiEncode.html","module_info":["core","codec"],"name":"AbiEncode","preview":"","type_name":"trait"},{"html_filename":"fn.encode.html","module_info":["core","codec"],"name":"encode","preview":"","type_name":"function"}],"impl_traits":[{"html_filename":"trait.Foo.html","module_info":["impl_traits","foo"],"name":"Foo","preview":"","type_name":"trait"},{"html_filename":"trait.Baz.html","module_info":["impl_traits","foo"],"name":"Baz","preview":"","type_name":"trait"},{"html_filename":"struct.Bar.html","module_info":["impl_traits","bar"],"name":"Bar","preview":"","type_name":"struct"}]};
"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=SEARCH_INDEX);"#]],
);
assert_file_tree(
Expand All @@ -50,8 +50,6 @@ fn test_impl_traits_default() {
"core/codec/struct.Buffer.html",
"core/codec/trait.AbiEncode.html",
"core/index.html",
"core/never/enum.Never.html",
"core/never/index.html",
"core/ops/index.html",
"core/ops/trait.Add.html",
"core/ops/trait.BitwiseAnd.html",
Expand Down
4 changes: 4 additions & 0 deletions sway-ast/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub enum Ty {
ampersand_token: AmpersandToken,
ty: Box<Ty>,
},
Never {
bang_token: BangToken,
},
}

impl Spanned for Ty {
Expand All @@ -43,6 +46,7 @@ impl Spanned for Ty {
ampersand_token,
ty,
} => Span::join(ampersand_token.span(), ty.span()),
Ty::Never { bang_token } => bang_token.span(),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/abi_generation/evm_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub fn abi_str(type_info: &TypeInfo, type_engine: &TypeEngine, decl_engine: &Dec
use TypeInfo::*;
match type_info {
Unknown => "unknown".into(),
Never => "never".into(),
UnknownGeneric { name, .. } => name.to_string(),
Placeholder(_) => "_".to_string(),
TypeParam(n) => format!("typeparam({n})"),
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/abi_generation/fuel_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ impl TypeInfo {
use TypeInfo::*;
match self {
Unknown => "unknown".into(),
Never => "never".into(),
UnknownGeneric { name, .. } => name.to_string(),
Placeholder(_) => "_".to_string(),
TypeParam(n) => format!("typeparam({n})"),
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1728,6 +1728,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> {
// XXX reassess all the places we use this
pub(crate) fn is_copy_type(&self, ty: &Type) -> bool {
ty.is_unit(self.context)
|| ty.is_never(self.context)
|| ty.is_bool(self.context)
|| ty
.get_uint_width(self.context)
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/ir_generation/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ fn convert_resolved_type(
convert_resolved_typeid(type_engine, decl_engine, context, &ty.type_id, span)?
}
TypeInfo::Ref(_) => Type::get_uint64(context),
TypeInfo::Never => Type::get_never(context),

// Unsupported types which shouldn't exist in the AST after type checking and
// monomorphisation.
Expand Down
12 changes: 0 additions & 12 deletions sway-core/src/language/ty/ast_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,6 @@ impl CollectTypesMetadata for TyAstNode {
}
}

impl DeterministicallyAborts for TyAstNode {
fn deterministically_aborts(&self, decl_engine: &DeclEngine, check_call_body: bool) -> bool {
use TyAstNodeContent::*;
match &self.content {
Declaration(_) => false,
Expression(exp) => exp.deterministically_aborts(decl_engine, check_call_body),
SideEffect(_) => false,
Error(_, _) => false,
}
}
}

impl GetDeclIdent for TyAstNode {
fn get_decl_ident(&self) -> Option<Ident> {
self.content.get_decl_ident()
Expand Down
10 changes: 1 addition & 9 deletions sway-core/src/language/ty/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use sway_types::Span;

use crate::{
decl_engine::*, engine_threading::*, language::ty::*, semantic_analysis::TypeCheckContext,
type_system::*, types::DeterministicallyAborts,
type_system::*,
};

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -74,11 +74,3 @@ impl UpdateConstantExpression for TyCodeBlock {
.for_each(|x| x.update_constant_expression(engines, implementing_type));
}
}

impl DeterministicallyAborts for TyCodeBlock {
fn deterministically_aborts(&self, decl_engine: &DeclEngine, check_call_body: bool) -> bool {
self.contents
.iter()
.any(|x| x.deterministically_aborts(decl_engine, check_call_body))
}
}
103 changes: 0 additions & 103 deletions sway-core/src/language/ty/expression/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,109 +317,6 @@ impl CollectTypesMetadata for TyExpression {
}
}

impl DeterministicallyAborts for TyExpression {
fn deterministically_aborts(&self, decl_engine: &DeclEngine, check_call_body: bool) -> bool {
use TyExpressionVariant::*;
match &self.expression {
FunctionApplication {
fn_ref, arguments, ..
} => {
if !check_call_body {
return false;
}
let function_decl = decl_engine.get_function(fn_ref);
function_decl
.body
.deterministically_aborts(decl_engine, check_call_body)
|| arguments
.iter()
.any(|(_, x)| x.deterministically_aborts(decl_engine, check_call_body))
}
Tuple { fields, .. } => fields
.iter()
.any(|x| x.deterministically_aborts(decl_engine, check_call_body)),
Array { contents, .. } => contents
.iter()
.any(|x| x.deterministically_aborts(decl_engine, check_call_body)),
CodeBlock(contents) => contents.deterministically_aborts(decl_engine, check_call_body),
LazyOperator { lhs, .. } => lhs.deterministically_aborts(decl_engine, check_call_body),
StructExpression { fields, .. } => fields.iter().any(|x| {
x.value
.deterministically_aborts(decl_engine, check_call_body)
}),
EnumInstantiation { contents, .. } => contents
.as_ref()
.map(|x| x.deterministically_aborts(decl_engine, check_call_body))
.unwrap_or(false),
AbiCast { address, .. } => {
address.deterministically_aborts(decl_engine, check_call_body)
}
StructFieldAccess { .. }
| Literal(_)
| StorageAccess { .. }
| VariableExpression { .. }
| ConstantExpression { .. }
| FunctionParameter
| TupleElemAccess { .. } => false,
IntrinsicFunction(kind) => kind.deterministically_aborts(decl_engine, check_call_body),
ArrayIndex { prefix, index } => {
prefix.deterministically_aborts(decl_engine, check_call_body)
|| index.deterministically_aborts(decl_engine, check_call_body)
}
AsmExpression { registers, .. } => registers.iter().any(|x| {
x.initializer
.as_ref()
.map(|x| x.deterministically_aborts(decl_engine, check_call_body))
.unwrap_or(false)
}),
MatchExp { desugared, .. } => {
desugared.deterministically_aborts(decl_engine, check_call_body)
}
IfExp {
condition,
then,
r#else,
..
} => {
condition.deterministically_aborts(decl_engine, check_call_body)
|| (then.deterministically_aborts(decl_engine, check_call_body)
&& r#else
.as_ref()
.map(|x| x.deterministically_aborts(decl_engine, check_call_body))
.unwrap_or(false))
}
AbiName(_) => false,
EnumTag { exp } => exp.deterministically_aborts(decl_engine, check_call_body),
UnsafeDowncast { exp, .. } => {
exp.deterministically_aborts(decl_engine, check_call_body)
}
WhileLoop { condition, body } => {
condition.deterministically_aborts(decl_engine, check_call_body)
|| body.deterministically_aborts(decl_engine, check_call_body)
}
ForLoop { desugared } => {
desugared.deterministically_aborts(decl_engine, check_call_body)
}
Break => false,
Continue => false,
Reassignment(reassignment) => reassignment
.rhs
.deterministically_aborts(decl_engine, check_call_body),
ImplicitReturn(exp) => exp.deterministically_aborts(decl_engine, check_call_body),
// TODO: Is this correct?
// I'm not sure what this function is supposed to do exactly. It's called
// "deterministically_aborts" which I thought meant it checks for an abort/panic, but
// it's actually checking for returns.
//
// Also, is it necessary to check the expression to see if avoids the return? eg.
// someone could write `return break;` in a loop, which would mean the return never
// gets executed.
Return(_) => true,
Ref(exp) | Deref(exp) => exp.deterministically_aborts(decl_engine, check_call_body),
}
}
}

impl TyExpression {
pub(crate) fn error(err: ErrorEmitted, span: Span, engines: &Engines) -> TyExpression {
let type_engine = engines.te();
Expand Down
14 changes: 1 addition & 13 deletions sway-core/src/language/ty/expression/intrinsic_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ use std::{
hash::{Hash, Hasher},
};

use crate::{
decl_engine::DeclEngine, engine_threading::*, language::ty::*, type_system::*, types::*,
};
use crate::{engine_threading::*, language::ty::*, type_system::*, types::*};
use itertools::Itertools;
use sway_ast::Intrinsic;
use sway_error::handler::{ErrorEmitted, Handler};
Expand Down Expand Up @@ -99,16 +97,6 @@ impl DebugWithEngines for TyIntrinsicFunctionKind {
}
}

impl DeterministicallyAborts for TyIntrinsicFunctionKind {
fn deterministically_aborts(&self, decl_engine: &DeclEngine, check_call_body: bool) -> bool {
matches!(self.kind, Intrinsic::Revert)
|| self
.arguments
.iter()
.any(|x| x.deterministically_aborts(decl_engine, check_call_body))
}
}

impl CollectTypesMetadata for TyIntrinsicFunctionKind {
fn collect_types_metadata(
&self,
Expand Down
2 changes: 0 additions & 2 deletions sway-core/src/semantic_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ mod program;
mod type_check_analysis;
pub(crate) mod type_check_context;
mod type_check_finalization;
mod type_check_unification;
pub use ast_node::*;
pub use namespace::Namespace;
pub(crate) use type_check_analysis::*;
pub(crate) use type_check_context::TypeCheckContext;
pub(crate) use type_check_finalization::*;
pub(crate) use type_check_unification::*;
Loading

0 comments on commit b32d0e0

Please sign in to comment.