diff --git a/sway-core/src/constants.rs b/sway-core/src/constants.rs index 533120905a7..8531ece0d11 100644 --- a/sway-core/src/constants.rs +++ b/sway-core/src/constants.rs @@ -17,3 +17,6 @@ pub const CONTRACT_CALL_COINS_PARAMETER_DEFAULT_VALUE: u64 = 0; pub const CONTRACT_CALL_ASSET_ID_PARAMETER_NAME: &str = "asset_id"; pub const CONTRACT_CALL_ASSET_ID_PARAMETER_DEFAULT_VALUE: [u8; 32] = [0; 32]; + +/// The default entry point for scripts and predicates. +pub const DEFAULT_ENTRY_POINT_FN_NAME: &str = "main"; diff --git a/sway-core/src/error.rs b/sway-core/src/error.rs index 829a97a5802..45b5609b2e5 100644 --- a/sway-core/src/error.rs +++ b/sway-core/src/error.rs @@ -713,7 +713,7 @@ pub enum CompileError { }, #[error("Unknown opcode: \"{op_name}\".")] UnrecognizedOp { op_name: Ident, span: Span }, - #[error("Generic type \"{ty}\" was unable to be inferred. Insufficient type information provided. Try annotating its type.")] + #[error("Cannot infer type for type parameter \"{ty}\". Insufficient type information provided. Try annotating its type.")] UnableToInferGeneric { ty: String, span: Span }, #[error("The value \"{val}\" is too large to fit in this 6-bit immediate spot.")] Immediate06TooLarge { val: u64, span: Span }, diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index 72be3487fbe..8eb0a1abf12 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -465,6 +465,24 @@ pub(crate) fn compile_ast_to_ir_to_asm( let mut warnings = Vec::new(); let mut errors = Vec::new(); + // the IR pipeline relies on type information being fully resolved. + // If type information is found to still be generic or unresolved inside of + // IR, this is considered an internal compiler error. To resolve this situation, + // we need to explicitly ensure all types are resolved before going into IR. + // + // We _could_ introduce a new type here that uses TypeInfo instead of TypeId and throw away + // the engine, since we don't need inference for IR. That'd be a _lot_ of copy-pasted code, + // though, so instead, we are just going to do a pass and throw any unresolved generics as + // errors and then hold as a runtime invariant that none of the types will be unresolved in the + // IR phase. + + check!( + ast.finalize_types(), + return err(warnings, errors), + warnings, + errors + ); + let mut ir = match optimize::compile_ast(ast) { Ok(ir) => ir, Err(e) => { @@ -478,7 +496,8 @@ pub(crate) fn compile_ast_to_ir_to_asm( // a selector. let mut functions_to_inline_to = Vec::new(); for (idx, fc) in &ir.functions { - if (matches!(tree_type, TreeType::Script | TreeType::Predicate) && fc.name == "main") + if (matches!(tree_type, TreeType::Script | TreeType::Predicate) + && fc.name == crate::constants::DEFAULT_ENTRY_POINT_FN_NAME) || (tree_type == TreeType::Contract && fc.selector.is_some()) { functions_to_inline_to.push(::sway_ir::function::Function(idx)); diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs index c66012dd05f..a0c215b9fa2 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs @@ -7,7 +7,7 @@ use crate::{ TypedExpressionVariant, TypedReturnStatement, TypedVariableDeclaration, VariableMutability, }, - create_new_scope, NamespaceWrapper, TypeCheckArguments, + create_new_scope, NamespaceWrapper, TypeCheckArguments, TypedAstNode, TypedAstNodeContent, }, type_engine::*, Ident, TypeParameter, @@ -37,6 +37,18 @@ pub struct TypedFunctionDeclaration { pub(crate) purity: Purity, } +impl From<&TypedFunctionDeclaration> for TypedAstNode { + fn from(o: &TypedFunctionDeclaration) -> Self { + let span = o.span.clone(); + TypedAstNode { + content: TypedAstNodeContent::Declaration(TypedDeclaration::FunctionDeclaration( + o.clone(), + )), + span, + } + } +} + // NOTE: Hash and PartialEq must uphold the invariant: // k1 == k2 -> hash(k1) == hash(k2) // https://doc.rust-lang.org/std/collections/struct.HashMap.html diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 6397efede1b..951b249f36a 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -43,6 +43,8 @@ pub(crate) fn error_recovery_expr(span: Span) -> TypedExpression { #[allow(clippy::too_many_arguments)] impl TypedExpression { + /// If this expression deterministically_aborts 100% of the time, this function returns + /// `true`. Used in dead-code and control-flow analysis. pub(crate) fn deterministically_aborts(&self) -> bool { use TypedExpressionVariant::*; match &self.expression { diff --git a/sway-core/src/semantic_analysis/ast_node/mod.rs b/sway-core/src/semantic_analysis/ast_node/mod.rs index e3c6003ba48..ca71c65747a 100644 --- a/sway-core/src/semantic_analysis/ast_node/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/mod.rs @@ -94,6 +94,35 @@ impl std::fmt::Display for TypedAstNode { } impl TypedAstNode { + /// Returns `true` if this AST node will be exported in a library, i.e. it is a public declaration. + pub(crate) fn is_public(&self) -> bool { + use TypedAstNodeContent::*; + match &self.content { + Declaration(decl) => decl.visibility().is_public(), + ReturnStatement(_) + | Expression(_) + | WhileLoop(_) + | SideEffect + | ImplicitReturnExpression(_) => false, + } + } + + /// Naive check to see if this node is a function declaration of a function called `main` if + /// the [TreeType] is Script or Predicate. + pub(crate) fn is_main_function(&self, tree_type: TreeType) -> bool { + match &self { + TypedAstNode { + content: + TypedAstNodeContent::Declaration(TypedDeclaration::FunctionDeclaration( + TypedFunctionDeclaration { name, .. }, + )), + .. + } if name.as_str() == crate::constants::DEFAULT_ENTRY_POINT_FN_NAME => { + matches!(tree_type, TreeType::Script | TreeType::Predicate) + } + _ => false, + } + } /// if this ast node _deterministically_ panics/aborts, then this is true. /// This is used to assist in type checking branches that abort control flow and therefore /// don't need to return a type. diff --git a/sway-core/src/semantic_analysis/syntax_tree.rs b/sway-core/src/semantic_analysis/syntax_tree.rs index 4ffd69d32ed..8bb6e04c6a6 100644 --- a/sway-core/src/semantic_analysis/syntax_tree.rs +++ b/sway-core/src/semantic_analysis/syntax_tree.rs @@ -86,6 +86,41 @@ impl TypedParseTree { } } + /// Ensures there are no unresolved types or types awaiting resolution in the AST. + pub(crate) fn finalize_types(&self) -> CompileResult<()> { + use TypedParseTree::*; + // Get all of the entry points for this tree type. For libraries, that's everything + // public. For contracts, ABI entries. For scripts and predicates, any function named `main`. + let errors: Vec<_> = match self { + Library { all_nodes, .. } => all_nodes + .iter() + .filter(|x| x.is_public()) + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect(), + Script { all_nodes, .. } => all_nodes + .iter() + .filter(|x| x.is_main_function(TreeType::Script)) + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect(), + Predicate { all_nodes, .. } => all_nodes + .iter() + .filter(|x| x.is_main_function(TreeType::Predicate)) + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect(), + Contract { abi_entries, .. } => abi_entries + .iter() + .map(TypedAstNode::from) + .flat_map(|x| x.check_for_unresolved_types()) + .collect(), + }; + + if errors.is_empty() { + ok((), vec![], errors) + } else { + err(vec![], errors) + } + } + pub(crate) fn type_check( parsed: ParseTree, new_namespace: NamespaceRef, diff --git a/sway-core/src/type_engine.rs b/sway-core/src/type_engine.rs index c566bfaea0f..98a1944eee4 100644 --- a/sway-core/src/type_engine.rs +++ b/sway-core/src/type_engine.rs @@ -1,16 +1,16 @@ use crate::error::*; - -use sway_types::span::Span; - use std::iter::FromIterator; +use sway_types::span::Span; mod engine; mod integer_bits; mod type_info; +mod unresolved_type_check; pub use engine::*; pub use integer_bits::*; use sway_types::Property; pub use type_info::*; +pub(crate) use unresolved_type_check::UnresolvedTypeCheck; /// A identifier to uniquely refer to our type terms pub type TypeId = usize; diff --git a/sway-core/src/type_engine/unresolved_type_check.rs b/sway-core/src/type_engine/unresolved_type_check.rs new file mode 100644 index 00000000000..d7a3a13526c --- /dev/null +++ b/sway-core/src/type_engine/unresolved_type_check.rs @@ -0,0 +1,248 @@ +//! This module handles the process of iterating through the typed AST and ensuring that all types +//! are well-defined and well-formed. This process is run on the AST before we pass it into the IR, +//! as the IR assumes all types are well-formed and will throw an ICE (internal compiler error) if +//! that is not the case. + +use crate::{semantic_analysis::*, type_engine::*}; + +/// If any types contained by this node are unresolved or have yet to be inferred, throw an +/// error to signal to the user that more type information is needed. +pub(crate) trait UnresolvedTypeCheck { + fn check_for_unresolved_types(&self) -> Vec; +} + +impl UnresolvedTypeCheck for TypeId { + fn check_for_unresolved_types(&self) -> Vec { + use TypeInfo::*; + match look_up_type_id(*self) { + UnknownGeneric { name } => vec![CompileError::UnableToInferGeneric { + ty: name.as_str().to_string(), + span: name.span().clone(), + }], + _ => vec![], + } + } +} + +impl UnresolvedTypeCheck for TypedAstNodeContent { + fn check_for_unresolved_types(&self) -> Vec { + use TypedAstNodeContent::*; + match self { + ReturnStatement(stmt) => stmt.expr.check_for_unresolved_types(), + Declaration(decl) => decl.check_for_unresolved_types(), + Expression(expr) => expr.check_for_unresolved_types(), + ImplicitReturnExpression(expr) => expr.check_for_unresolved_types(), + WhileLoop(lo) => { + let mut condition = lo.condition.check_for_unresolved_types(); + let mut body = lo + .body + .contents + .iter() + .flat_map(TypedAstNode::check_for_unresolved_types) + .collect(); + condition.append(&mut body); + condition + } + SideEffect => vec![], + } + } +} + +impl UnresolvedTypeCheck for TypedExpression { + fn check_for_unresolved_types(&self) -> Vec { + use TypedExpressionVariant::*; + match &self.expression { + TypeProperty { type_id, .. } => type_id.check_for_unresolved_types(), + FunctionApplication { + arguments, + function_body, + .. + } => { + let mut args = arguments + .iter() + .map(|x| &x.1) + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect::>(); + args.append( + &mut function_body + .contents + .iter() + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect(), + ); + args + } + // expressions don't ever have return types themselves, they're stored in + // `TypedExpression::return_type`. Variable expressions are just names of variables. + VariableExpression { .. } => vec![], + Tuple { fields } => fields + .iter() + .flat_map(|x| x.check_for_unresolved_types()) + .collect(), + AsmExpression { registers, .. } => registers + .iter() + .filter_map(|x| x.initializer.as_ref()) + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect::>(), + StructExpression { fields, .. } => fields + .iter() + .flat_map(|x| x.value.check_for_unresolved_types()) + .collect(), + LazyOperator { lhs, rhs, .. } => lhs + .check_for_unresolved_types() + .into_iter() + .chain(rhs.check_for_unresolved_types().into_iter()) + .collect(), + Array { contents } => contents + .iter() + .flat_map(|x| x.check_for_unresolved_types()) + .collect(), + ArrayIndex { prefix, .. } => prefix.check_for_unresolved_types(), + CodeBlock(block) => block + .contents + .iter() + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect(), + IfExp { + condition, + then, + r#else, + } => { + let mut buf = condition + .check_for_unresolved_types() + .into_iter() + .chain(then.check_for_unresolved_types().into_iter()) + .collect::>(); + if let Some(r#else) = r#else { + buf.append(&mut r#else.check_for_unresolved_types()); + } + buf + } + StructFieldAccess { + prefix, + resolved_type_of_parent, + .. + } => prefix + .check_for_unresolved_types() + .into_iter() + .chain( + resolved_type_of_parent + .check_for_unresolved_types() + .into_iter(), + ) + .collect(), + IfLet { + enum_type, + expr, + then, + r#else, + .. + } => { + let mut buf = enum_type + .check_for_unresolved_types() + .into_iter() + .chain(expr.check_for_unresolved_types().into_iter()) + .chain( + then.contents + .iter() + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .into_iter(), + ) + .collect::>(); + if let Some(el) = r#else { + buf.append(&mut el.check_for_unresolved_types()); + } + buf + } + TupleElemAccess { + prefix, + resolved_type_of_parent, + .. + } => prefix + .check_for_unresolved_types() + .into_iter() + .chain( + resolved_type_of_parent + .check_for_unresolved_types() + .into_iter(), + ) + .collect(), + EnumInstantiation { + enum_decl, + contents, + .. + } => { + let mut buf = if let Some(contents) = contents { + contents.check_for_unresolved_types().into_iter().collect() + } else { + vec![] + }; + buf.append( + &mut enum_decl + .variants + .iter() + .flat_map(|x| x.r#type.check_for_unresolved_types()) + .collect(), + ); + buf + } + SizeOfValue { expr } => expr.check_for_unresolved_types(), + AbiCast { address, .. } => address.check_for_unresolved_types(), + // storage access can never be generic + StorageAccess { .. } | Literal(_) | AbiName(_) | FunctionParameter => vec![], + } + } +} +impl UnresolvedTypeCheck for TypedAstNode { + fn check_for_unresolved_types(&self) -> Vec { + self.content.check_for_unresolved_types() + } +} +impl UnresolvedTypeCheck for TypedDeclaration { + // this is only run on entry nodes, which must have all well-formed types + fn check_for_unresolved_types(&self) -> Vec { + use TypedDeclaration::*; + match self { + VariableDeclaration(decl) => { + let mut body = decl.body.check_for_unresolved_types(); + body.append(&mut decl.type_ascription.check_for_unresolved_types()); + body + } + FunctionDeclaration(decl) => { + let mut body: Vec = decl + .body + .contents + .iter() + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect(); + body.append(&mut decl.return_type.check_for_unresolved_types()); + body.append( + &mut decl + .type_parameters + .iter() + .map(|x| &x.type_id) + .flat_map(UnresolvedTypeCheck::check_for_unresolved_types) + .collect(), + ); + body + } + ConstantDeclaration(TypedConstantDeclaration { value, .. }) => { + value.check_for_unresolved_types() + } + StorageReassignment(TypeCheckedStorageReassignment { fields, rhs, .. }) => fields + .iter() + .flat_map(|x| x.r#type.check_for_unresolved_types()) + .chain(rhs.check_for_unresolved_types().into_iter()) + .collect(), + Reassignment(TypedReassignment { rhs, .. }) => rhs.check_for_unresolved_types(), + ErrorRecovery + | StorageDeclaration(_) + | TraitDeclaration(_) + | StructDeclaration(_) + | EnumDeclaration(_) + | ImplTrait { .. } + | AbiDeclaration(_) + | GenericTypeForFunctionScope { .. } => vec![], + } + } +} diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index 2e79f84580a..db8a3092ceb 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -415,6 +415,7 @@ pub fn run(filter_regex: Option) { "should_fail/generics_unhelpful_error", "should_fail/generic_shadows_generic", "should_fail/different_contract_caller_types", + "should_fail/insufficient_type_info", "should_fail/primitive_type_argument", ]; number_of_tests_run += negative_project_names.iter().fold(0, |acc, name| { diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/.gitignore b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/Forc.lock new file mode 100644 index 00000000000..40c48494ae0 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/Forc.lock @@ -0,0 +1,11 @@ +[[package]] +name = 'core' +dependencies = [] + +[[package]] +name = 'insufficient_type_info' +dependencies = ['std'] + +[[package]] +name = 'std' +dependencies = ['core'] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/Forc.toml new file mode 100644 index 00000000000..42157ea0dec --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "insufficient_type_info" + +[dependencies] +std = { path = "../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/src/main.sw new file mode 100644 index 00000000000..92aaab678c8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/insufficient_type_info/src/main.sw @@ -0,0 +1,9 @@ +script; + +fn foo() { + let x = size_of::(); +} + +fn main() { + foo() +}