From e8e6625d3bca2d82a871839f358105c677bee6ad Mon Sep 17 00:00:00 2001 From: Rigidity Date: Wed, 17 Apr 2024 20:58:05 -0400 Subject: [PATCH] Part 1 of testing --- crates/rue-compiler/src/error.rs | 68 +++++++++++------------ crates/rue-compiler/src/lowerer.rs | 77 ++++++++++++++++----------- crates/rue-compiler/src/optimizer.rs | 50 ++++++++++++----- crates/rue-compiler/src/scope.rs | 10 ++-- tests.toml | 33 ++++++++++++ tests/errors/missing_main.rue | 0 tests/errors/recursive_type_alias.rue | 11 ++++ tests/warnings/unused_symbols.rue | 27 ++++++++++ tests/warnings/unused_types.rue | 25 +++++++++ 9 files changed, 216 insertions(+), 85 deletions(-) create mode 100644 tests/errors/missing_main.rue create mode 100644 tests/errors/recursive_type_alias.rue create mode 100644 tests/warnings/unused_symbols.rue create mode 100644 tests/warnings/unused_types.rue diff --git a/crates/rue-compiler/src/error.rs b/crates/rue-compiler/src/error.rs index 77706e3..69910cf 100644 --- a/crates/rue-compiler/src/error.rs +++ b/crates/rue-compiler/src/error.rs @@ -38,12 +38,6 @@ pub enum DiagnosticKind { #[derive(Debug, Error, Clone, PartialEq, Eq, Hash)] pub enum WarningKind { - #[error("marking optional types as optional again has no effect")] - UselessOptional, - - #[error("redundant type check against `{0}`, value is already that type")] - RedundantTypeGuard(String), - #[error("unused function `{0}`")] UnusedFunction(String), @@ -56,8 +50,23 @@ pub enum WarningKind { #[error("unused let binding `{0}`")] UnusedLet(String), - #[error("unused type `{0}`")] - UnusedType(String), + #[error("unused enum `{0}`")] + UnusedEnum(String), + + #[error("unused enum variant `{0}`")] + UnusedEnumVariant(String), + + #[error("unused struct `{0}`")] + UnusedStruct(String), + + #[error("unused type alias `{0}`")] + UnusedTypeAlias(String), + + #[error("marking optional types as optional again has no effect")] + UselessOptionalType, + + #[error("redundant type check against `{0}`, value is already that type")] + RedundantTypeCheck(String), } #[derive(Debug, Error, Clone, PartialEq, Eq, Hash)] @@ -65,19 +74,16 @@ pub enum ErrorKind { #[error("missing `main` function")] MissingMain, - #[error("undefined reference `{0}`")] + #[error("cannot reference undefined symbol `{0}`")] UndefinedReference(String), - #[error("undefined type `{0}`")] + #[error("cannot reference undefined type `{0}`")] UndefinedType(String), - #[error("recursive type alias")] + #[error("type aliases cannot reference themselves recursively")] RecursiveTypeAlias, - #[error("expected {expected} arguments, found {found}")] - ArgumentMismatch { expected: usize, found: usize }, - - #[error("expected type `{expected}`, found `{found}`")] + #[error("expected type `{expected}`, but found `{found}`")] TypeMismatch { expected: String, found: String }, #[error("cannot cast type `{found}` to `{expected}`")] @@ -86,33 +92,27 @@ pub enum ErrorKind { #[error("cannot call expression with type `{0}`")] UncallableType(String), + #[error("expected {expected} arguments, but found {found}")] + ArgumentMismatch { expected: usize, found: usize }, + #[error("uninitializable type `{0}`")] UninitializableType(String), #[error("duplicate field `{0}`")] DuplicateField(String), - #[error("undefined field `{0}`")] - UndefinedField(String), + #[error("undefined field `{field}` on type `{ty}`")] + UndefinedField { field: String, ty: String }, - #[error("missing fields: {}", join_names(.0))] - MissingFields(Vec), + #[error("missing fields on type `{ty}`: {}", join_names(.fields))] + MissingFields { fields: Vec, ty: String }, - #[error("cannot access named field of non-struct type `{0}`")] - NonStructFieldAccess(String), + #[error("cannot access field `{field}` of non-struct type `{ty}`")] + InvalidFieldAccess { field: String, ty: String }, - #[error("unknown field of pair type `{0}`, expected `first` or `rest`")] - PairFieldAccess(String), - - #[error("unknown field of bytes type `{0}`, expected `length`")] - BytesFieldAccess(String), - - #[error("cannot index non-list type `{0}`")] + #[error("cannot index into non-list type `{0}`")] IndexAccess(String), - #[error("index `{0}` out of bounds, length is `{1}`")] - IndexOutOfBounds(u32, u32), - #[error("the spread operator can only be used on the last element")] NonFinalSpread, @@ -125,15 +125,15 @@ pub enum ErrorKind { #[error("duplicate enum variant `{0}`")] DuplicateEnumVariant(String), + #[error("unknown enum variant `{0}`")] + UnknownEnumVariant(String), + #[error("paths are not allowed in this context")] PathNotAllowed, #[error("cannot path into non-enum type `{0}`")] PathIntoNonEnum(String), - #[error("unknown enum variant `{0}`")] - UnknownEnumVariant(String), - #[error("cannot check type `{from}` against `{to}`")] UnsupportedTypeGuard { from: String, to: String }, diff --git a/crates/rue-compiler/src/lowerer.rs b/crates/rue-compiler/src/lowerer.rs index 74893a6..23cdf67 100644 --- a/crates/rue-compiler/src/lowerer.rs +++ b/crates/rue-compiler/src/lowerer.rs @@ -756,6 +756,7 @@ impl<'a> Lowerer<'a> { match ty.map(|ty| self.db.ty(ty)).cloned() { Some(Type::Struct(struct_type)) => { let hir_id = self.compile_initializer_fields( + ty.unwrap(), struct_type.fields(), initializer.fields(), initializer.syntax().text_range(), @@ -768,6 +769,7 @@ impl<'a> Lowerer<'a> { } Some(Type::EnumVariant(enum_variant)) => { let fields_hir_id = self.compile_initializer_fields( + ty.unwrap(), enum_variant.fields(), initializer.fields(), initializer.syntax().text_range(), @@ -796,6 +798,7 @@ impl<'a> Lowerer<'a> { /// Compiles the fields of an initializer into a list. fn compile_initializer_fields( &mut self, + struct_type: TypeId, struct_fields: &IndexMap, initializer_fields: Vec, text_range: TextRange, @@ -831,7 +834,10 @@ impl<'a> Lowerer<'a> { ); } else if !struct_fields.contains_key(name.text()) { self.error( - ErrorKind::UndefinedField(name.to_string()), + ErrorKind::UndefinedField { + field: name.to_string(), + ty: self.type_name(struct_type), + }, name.text_range(), ); } else { @@ -847,7 +853,13 @@ impl<'a> Lowerer<'a> { .collect(); if !missing_fields.is_empty() { - self.error(ErrorKind::MissingFields(missing_fields), text_range); + self.error( + ErrorKind::MissingFields { + fields: missing_fields, + ty: self.type_name(struct_type), + }, + text_range, + ); } let mut hir_id = self.nil_hir; @@ -882,46 +894,44 @@ impl<'a> Lowerer<'a> { Type::Struct(struct_type) => { if let Some(field) = struct_type.fields().get_full(field_name.text()) { let (index, _, field_type) = field; - Value::typed(self.compile_index(value.hir(), index, false), *field_type) + return Value::typed( + self.compile_index(value.hir(), index, false), + *field_type, + ); } else { self.error( - ErrorKind::UndefinedField(field_name.to_string()), + ErrorKind::UndefinedField { + field: field_name.to_string(), + ty: self.type_name(value.ty()), + }, field_name.text_range(), ); - self.unknown() + return self.unknown(); } } Type::Pair(left, right) => match field_name.text() { - "first" => Value::typed(self.db.alloc_hir(Hir::First(value.hir())), left), - "rest" => Value::typed(self.db.alloc_hir(Hir::Rest(value.hir())), right), - _ => { - self.error( - ErrorKind::PairFieldAccess(field_name.to_string()), - field_name.text_range(), - ); - self.unknown() + "first" => { + return Value::typed(self.db.alloc_hir(Hir::First(value.hir())), left); } - }, - Type::Bytes | Type::Bytes32 => match field_name.text() { - "length" => { - Value::typed(self.db.alloc_hir(Hir::Strlen(value.hir())), self.int_type) - } - _ => { - self.error( - ErrorKind::BytesFieldAccess(field_name.to_string()), - field_name.text_range(), - ); - self.unknown() + "rest" => { + return Value::typed(self.db.alloc_hir(Hir::Rest(value.hir())), right); } + _ => {} }, - _ => { - self.error( - ErrorKind::NonStructFieldAccess(self.type_name(value.ty())), - field_name.text_range(), - ); - self.unknown() + Type::Bytes | Type::Bytes32 if field_name.text() == "length" => { + return Value::typed(self.db.alloc_hir(Hir::Strlen(value.hir())), self.int_type); } + _ => {} } + + self.error( + ErrorKind::InvalidFieldAccess { + field: field_name.to_string(), + ty: self.type_name(value.ty()), + }, + field_name.text_range(), + ); + self.unknown() } fn compile_index_access(&mut self, index_access: IndexAccess) -> Value { @@ -1203,7 +1213,7 @@ impl<'a> Lowerer<'a> { ) -> Option<(Guard, HirId)> { if self.types_equal(from, to) { self.warning( - WarningKind::RedundantTypeGuard(self.type_name(from)), + WarningKind::RedundantTypeCheck(self.type_name(from)), text_range, ); return Some((Guard::new(to, self.bool_type), hir_id)); @@ -1923,7 +1933,10 @@ impl<'a> Lowerer<'a> { .unwrap_or(self.unknown_type); if let Type::Optional(inner) = self.db.ty_raw(ty).clone() { - self.warning(WarningKind::UselessOptional, optional.syntax().text_range()); + self.warning( + WarningKind::UselessOptionalType, + optional.syntax().text_range(), + ); return inner; } diff --git a/crates/rue-compiler/src/optimizer.rs b/crates/rue-compiler/src/optimizer.rs index 5ebca2d..a83d9b0 100644 --- a/crates/rue-compiler/src/optimizer.rs +++ b/crates/rue-compiler/src/optimizer.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use indexmap::IndexSet; use rowan::TextRange; @@ -8,6 +8,7 @@ use crate::{ hir::{BinOp, Hir}, lir::Lir, symbol::Symbol, + ty::Type, Diagnostic, DiagnosticKind, TypeId, WarningKind, }; @@ -16,8 +17,8 @@ struct Environment { definitions: IndexSet, captures: IndexSet, parameters: IndexSet, - used_symbols: HashSet, - used_types: HashSet, + used_symbols: IndexSet, + used_types: IndexSet, varargs: bool, inherits_from: Option, } @@ -94,8 +95,8 @@ impl<'a> Optimizer<'a> { fn check_unused( &mut self, scope_id: ScopeId, - used_symbols: HashSet, - used_types: HashSet, + used_symbols: IndexSet, + used_types: IndexSet, ) { let unused_symbols: Vec = self .db @@ -109,11 +110,19 @@ impl<'a> Optimizer<'a> { for symbol_id in unused_symbols { if let Some(token) = self.db.symbol_token(symbol_id) { match self.db.symbol(symbol_id).clone() { - Symbol::Function { .. } => { + Symbol::Function { + scope_id: function_scope_id, + hir_id, + .. + } => { self.warning( WarningKind::UnusedFunction(token.to_string()), token.text_range(), ); + + // Even though it's unused, we should check for captures. + // This is so we can warn about unused captures within the function scope. + self.compute_captures_entrypoint(function_scope_id, None, hir_id); } Symbol::Parameter { .. } => { self.warning( @@ -148,10 +157,25 @@ impl<'a> Optimizer<'a> { for type_id in unused_types { if let Some(token) = self.db.type_token(type_id) { - self.warning( - WarningKind::UnusedType(token.to_string()), - token.text_range(), - ); + match self.db.ty_raw(type_id) { + Type::Alias(..) => self.warning( + WarningKind::UnusedTypeAlias(token.to_string()), + token.text_range(), + ), + Type::Enum(..) => self.warning( + WarningKind::UnusedEnum(token.to_string()), + token.text_range(), + ), + Type::EnumVariant(..) => self.warning( + WarningKind::UnusedEnumVariant(token.to_string()), + token.text_range(), + ), + Type::Struct(..) => self.warning( + WarningKind::UnusedStruct(token.to_string()), + token.text_range(), + ), + _ => {} + } } } } @@ -293,10 +317,8 @@ impl<'a> Optimizer<'a> { let mut args = Vec::new(); - for symbol_id in self.db.scope(scope_id).local_symbols() { - if self.db.symbol(symbol_id).is_definition() { - args.push(self.opt_definition(scope_id, symbol_id)); - } + for symbol_id in self.env(scope_id).definitions.clone() { + args.push(self.opt_definition(scope_id, symbol_id)); } for symbol_id in self.env(scope_id).captures.clone() { diff --git a/crates/rue-compiler/src/scope.rs b/crates/rue-compiler/src/scope.rs index 7bb8c1d..23824d9 100644 --- a/crates/rue-compiler/src/scope.rs +++ b/crates/rue-compiler/src/scope.rs @@ -1,6 +1,6 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; -use indexmap::IndexSet; +use indexmap::{IndexMap, IndexSet}; use crate::{database::TypeId, SymbolId}; @@ -8,9 +8,9 @@ use crate::{database::TypeId, SymbolId}; pub struct Scope { symbol_table: HashMap, type_aliases: HashMap, - type_names: HashMap, + type_names: IndexMap, local_symbols: IndexSet, - used_types: HashSet, + used_types: IndexSet, } impl Scope { @@ -52,7 +52,7 @@ impl Scope { self.used_types.insert(type_id); } - pub fn used_types(&self) -> &HashSet { + pub fn used_types(&self) -> &IndexSet { &self.used_types } } diff --git a/tests.toml b/tests.toml index 53f598f..7a16c71 100644 --- a/tests.toml +++ b/tests.toml @@ -130,3 +130,36 @@ cost = 4755 input = "(0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 () () ())" output = "((g1_multiply 0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a))" hash = "41ad4e730b68dcbd7cb2440343bc11c1b31eb5951273dac3ab93a00d99019ac6" + +[unused_symbols] +parser_errors = [] +compiler_errors = [ + "unused let binding `unused_let` at 5:9", + "unused constant `UNUSED_INNER_CONSTANT` at 6:11", + "unused function `unused_inner_function` at 8:9", + "unused function `unused_inner_inner_function` at 9:13", + "unused function `unused_deeper_function` at 10:17", + "unused constant `UNUSED_INDIRECT_CONSTANT` at 1:7", + "unused constant `UNUSED_CONSTANT` at 2:7", + "unused function `unused_indirect_function` at 21:5", + "unused function `unused_function` at 25:5", +] + +[unused_types] +parser_errors = [] +compiler_errors = [ + "unused type alias `A` at 2:6", + "unused enum `Some` at 6:6", + "unused struct `Thing` at 17:8", +] + +[recursive_type_alias] +parser_errors = [] +compiler_errors = [ + "type aliases cannot reference themselves recursively at 4:1", + "type aliases cannot reference themselves recursively at 7:1", +] + +[missing_main] +parser_errors = [] +compiler_errors = ["missing `main` function at 1:1"] diff --git a/tests/errors/missing_main.rue b/tests/errors/missing_main.rue new file mode 100644 index 0000000..e69de29 diff --git a/tests/errors/recursive_type_alias.rue b/tests/errors/recursive_type_alias.rue new file mode 100644 index 0000000..c1c0018 --- /dev/null +++ b/tests/errors/recursive_type_alias.rue @@ -0,0 +1,11 @@ +type A = B; +type B = C; +type C = D; +type D = A; + +type E = F; +type F = E; + +fun main() -> A { + true +} diff --git a/tests/warnings/unused_symbols.rue b/tests/warnings/unused_symbols.rue new file mode 100644 index 0000000..e6b0519 --- /dev/null +++ b/tests/warnings/unused_symbols.rue @@ -0,0 +1,27 @@ +const UNUSED_INDIRECT_CONSTANT: Int = 42; +const UNUSED_CONSTANT: Int = UNUSED_INDIRECT_CONSTANT; + +fun main() -> Nil { + let unused_let = UNUSED_CONSTANT + UNUSED_INDIRECT_CONSTANT; + const UNUSED_INNER_CONSTANT: Int = UNUSED_CONSTANT + UNUSED_INDIRECT_CONSTANT; + + fun unused_inner_function() -> Nil { + fun unused_inner_inner_function() -> Nil { + fun unused_deeper_function() -> Nil { + nil + } + unused_function() + } + unused_function() + } + + nil +} + +fun unused_indirect_function() -> Nil { + nil +} + +fun unused_function() -> Nil { + unused_indirect_function() +} diff --git a/tests/warnings/unused_types.rue b/tests/warnings/unused_types.rue new file mode 100644 index 0000000..6c60358 --- /dev/null +++ b/tests/warnings/unused_types.rue @@ -0,0 +1,25 @@ +type C = D; +type A = B; +type B = C; +type D = Int; + +enum Some { + A = 1 { + a: Int, + }, + B = 2 { + b: Bytes, + c: (Nil, (Nil, Nil)), + }, + C = 3 {}, +} + +struct Thing { + first: PublicKey, + next: Bytes32, + last: Bool, +} + +fun main() -> D { + 42 +}