diff --git a/src/api/command.rs b/src/api/command.rs index 71d095daf..37a380f1b 100644 --- a/src/api/command.rs +++ b/src/api/command.rs @@ -491,6 +491,7 @@ macro_rules! command { #[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))] #[cfg_attr(feature = "bon", derive(bon::Builder))] #[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] + #[must_use = "Commands do nothing unless executed via a `Driver`."] $(#[$($meta)*])* pub struct $name { $($(#[$($field_meta)*])* $field_vis $field_name: $field_ty, )* diff --git a/src/api/error.rs b/src/api/error.rs index dbe27ec28..701217fdd 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -21,6 +21,7 @@ pub struct ApiError { impl ArchivedApiError { /// Get the error code for this error. #[inline] + #[must_use] pub const fn code(&self) -> ApiErrorCode { self.code.get() } @@ -58,6 +59,7 @@ macro_rules! error_codes { impl $name { /// Get the HTTP status code for this error code. + #[must_use] pub fn http_status(self) -> StatusCode { match self { $(Self::$variant => $status,)* diff --git a/src/client/mod.rs b/src/client/mod.rs index 3f7a78812..4d5d2e26e 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -19,6 +19,7 @@ struct ClientInner { preferred_encoding: ArcSwap, } +#[must_use = "Client does nothing on its own."] #[derive(Clone)] pub struct Client(Arc); @@ -63,6 +64,7 @@ impl Client { Ok(()) } + #[must_use] pub fn auth(&self) -> Option { self.0.auth.load().as_ref().map(|auth| auth.0) } diff --git a/src/driver/error.rs b/src/driver/error.rs index c4a4e5fea..2294effbf 100644 --- a/src/driver/error.rs +++ b/src/driver/error.rs @@ -44,6 +44,7 @@ pub enum DriverError { } impl DriverError { + #[must_use] pub fn is_not_found(&self) -> bool { match self { DriverError::ApiError(err) => err.code == ApiErrorCode::NotFound, diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 296659347..6bf72487f 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -23,6 +23,7 @@ pub enum Encoding { CBOR, } +#[must_use = "This struct does nothing on its own. Use `Driver::execute` to send a request."] #[derive(Clone)] pub struct Driver { pub(crate) inner: reqwest::Client, diff --git a/src/framework/standard/ctx.rs b/src/framework/standard/ctx.rs index 16769570f..7f3cd4644 100644 --- a/src/framework/standard/ctx.rs +++ b/src/framework/standard/ctx.rs @@ -45,6 +45,7 @@ impl StandardContext { self.client().driver() } + #[must_use] pub fn close(&self) -> bool { self.inner().tx.send(StandardResponse::Close).is_ok() } diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 934f3dd3c..ae5b26a7b 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -44,6 +44,7 @@ pub struct Standard { } impl Standard, E> { + #[must_use] pub fn new(client: Client) -> Self { Self::new_with_state(client, ()) } diff --git a/src/framework_utils/args.rs b/src/framework_utils/args.rs index 7e7f50f51..d02b21bb9 100644 --- a/src/framework_utils/args.rs +++ b/src/framework_utils/args.rs @@ -42,30 +42,37 @@ impl<'a> Argument<'a> { } } + #[must_use] pub fn orig(&self) -> &'a str { self.buf } + #[must_use] pub fn inner_str(&self) -> &'a str { &self.buf[self.inner()] } + #[must_use] pub fn outer_str(&self) -> &'a str { &self.buf[self.outer()] } + #[must_use] pub fn inner(&self) -> Range { self.inner_start..self.inner_end } + #[must_use] pub fn outer(&self) -> Range { self.outer_start..self.outer_end } + #[must_use] pub fn is_quoted(&self) -> bool { self.inner() != self.outer() } + #[must_use] pub fn is_quoted_with(&self, (start, end): (char, char)) -> bool { let outer = self.outer_str(); @@ -74,10 +81,12 @@ impl<'a> Argument<'a> { } impl<'a> ArgumentSplitter<'a> { + #[must_use] pub fn orig(&self) -> &'a str { self.buf } + #[must_use] pub fn arguments(&self) -> &[Argument<'a>] { &self.arguments } @@ -89,10 +98,12 @@ impl<'a> ArgumentSplitter<'a> { self.arguments.iter().map(|r| r.inner_str()) } + #[must_use] pub fn split(args: &'a str) -> Self { Self::split_delimiters(args, &[('`', '`'), ('"', '"'), ('\u{201C}', '\u{201D}')]) } + #[must_use] pub fn split_delimiters(args: &'a str, delmiters: &[(char, char)]) -> Self { let buf = args; let mut arguments = SmallVec::new(); diff --git a/src/gateway/conn.rs b/src/gateway/conn.rs index 20dc78182..1330bdcea 100644 --- a/src/gateway/conn.rs +++ b/src/gateway/conn.rs @@ -54,6 +54,7 @@ impl GatewayConnectionControl { } impl GatewayConnection { + #[must_use] pub fn new(client: Client) -> GatewayConnection { GatewayConnection { client, diff --git a/src/lib.rs b/src/lib.rs index 461927bd7..82aee3143 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ //! Lantern Chat Client SDK //#![cfg_attr(not(feature = "std"), no_std)] +#![warn(clippy::perf, clippy::must_use_candidate, clippy::complexity, clippy::suspicious)] #![allow(clippy::bad_bit_mask)] extern crate alloc; diff --git a/src/models/asset.rs b/src/models/asset.rs index ca5b52958..911cacc7a 100644 --- a/src/models/asset.rs +++ b/src/models/asset.rs @@ -61,6 +61,7 @@ impl_sql_for_bitflags!(AssetFlags); impl AssetFlags { /// Sets the quality of the asset, clamping to `[0-128)` + #[must_use] pub const fn with_quality(self, q: u8) -> Self { self.intersection(Self::QUALITY.complement()).union(if q < 128 { AssetFlags::from_bits_truncate(q as i16) @@ -70,6 +71,7 @@ impl AssetFlags { } /// Sets the alpha channel flag + #[must_use] pub const fn with_alpha(&self, has_alpha: bool) -> Self { if has_alpha { self.union(Self::HAS_ALPHA) @@ -79,11 +81,13 @@ impl AssetFlags { } /// Gets the quality value from the asset flags + #[must_use] pub const fn quality(&self) -> u8 { self.intersection(Self::QUALITY).bits() as u8 } /// Constructs a new `AssetFlags` from the given extension + #[must_use] pub fn from_ext(ext: &str) -> Self { static FORMAT_EXTS: &[(AssetFlags, &str)] = &[ (AssetFlags::FORMAT_PNG, "png"), diff --git a/src/models/auth.rs b/src/models/auth.rs index 067b1a788..83a64e6be 100644 --- a/src/models/auth.rs +++ b/src/models/auth.rs @@ -49,6 +49,7 @@ impl fmt::Display for InvalidAuthToken { impl core::error::Error for InvalidAuthToken {} impl AuthToken { + #[must_use] pub fn raw_header(&self) -> arrayvec::ArrayString<{ MAX_LENGTH }> { let (prefix, value) = match self { AuthToken::Bearer(ref token) => (BEARER_PREFIX, token.as_ref()), diff --git a/src/models/embed/mod.rs b/src/models/embed/mod.rs index 4f626aec6..f6aff06ad 100644 --- a/src/models/embed/mod.rs +++ b/src/models/embed/mod.rs @@ -19,6 +19,7 @@ pub mod v1; pub use v1::*; impl Embed { + #[must_use] pub fn url(&self) -> Option<&str> { match self { Embed::V1(embed) => embed.url.as_ref().map(|x| x as _), diff --git a/src/models/embed/v1.rs b/src/models/embed/v1.rs index 0ea21388e..96d57510f 100644 --- a/src/models/embed/v1.rs +++ b/src/models/embed/v1.rs @@ -184,6 +184,7 @@ pub struct EmbedV1 { } impl EmbedV1 { + #[must_use] pub fn has_fullsize_media(&self) -> bool { !EmbedMedia::is_empty(&self.obj) || !EmbedMedia::is_empty(&self.img) @@ -192,6 +193,7 @@ impl EmbedV1 { } // NOTE: Provider, canonical, and title can be skipped here, as by themselves it's a very boring embed + #[must_use] pub fn is_plain_link(&self) -> bool { if self.ty != EmbedType::Link || self.url.is_none() @@ -528,6 +530,7 @@ impl EmbedMedia { } } + #[must_use] pub fn is_empty(this: &Option>) -> bool { match this { Some(ref e) => e.url.is_empty(), @@ -570,12 +573,14 @@ pub struct EmbedProvider { } impl EmbedProvider { + #[must_use] pub fn is_none(&self) -> bool { is_none_or_empty(&self.name) && IsNoneOrEmpty::is_none_or_empty(&self.url) && EmbedMedia::is_empty(&self.icon) } } impl EmbedAuthor { + #[must_use] pub fn is_none(this: &Option) -> bool { match this { Some(ref this) => { @@ -644,6 +649,7 @@ pub struct EmbedField { } impl EmbedField { + #[must_use] pub fn is_empty(&self) -> bool { (self.name.is_empty() || self.value.is_empty()) && EmbedMedia::is_empty(&self.img) } diff --git a/src/models/gateway.rs b/src/models/gateway.rs index 30909896c..c131696fb 100644 --- a/src/models/gateway.rs +++ b/src/models/gateway.rs @@ -405,7 +405,7 @@ pub mod message { } } - #[inline(always)] + #[inline(always)] #[must_use] pub fn state(&self) -> &S { &self.state } @@ -486,6 +486,7 @@ pub mod message { impl $name { /// Returns the discrete opcode for the message + #[must_use] pub const fn opcode(&self) -> [<$name Opcode>] { match self { $($name::$opcode(_) => [<$name Opcode>]::$opcode,)* @@ -505,7 +506,7 @@ pub mod message { #[doc = "Create new [`" $opcode "`](" $name "::" $opcode ") message from payload fields."] #[doc = ""] $(#[$variant_meta])* - #[inline] + #[inline] #[must_use] pub fn []($($field: impl Into<$ty>),*) -> Self { $name::$opcode([<$name:snake _payloads>]::[<$opcode Payload>] { $($field: $field.into()),* @@ -695,7 +696,7 @@ pub mod message { } impl ServerMsg { - #[rustfmt::skip] + #[rustfmt::skip] #[must_use] pub fn matching_intent(&self) -> Option { Some(match *self { | ServerMsg::PartyCreate { .. } @@ -753,6 +754,7 @@ pub mod message { } /// If the event originated from a specific user, get their ID + #[must_use] pub fn user_id(&self) -> Option { Some(match self { ServerMsg::MemberAdd(e) => e.member.user.id, diff --git a/src/models/message.rs b/src/models/message.rs index a3df72b9a..b2daf8418 100644 --- a/src/models/message.rs +++ b/src/models/message.rs @@ -32,6 +32,7 @@ impl_sql_for_bitflags!(MessageFlags); impl MessageFlags { #[inline] + #[must_use] pub const fn from_bits_truncate_public(bits: i32) -> Self { Self::from_bits_truncate(bits).difference(Self::LANGUAGE) } diff --git a/src/models/nullable.rs b/src/models/nullable.rs index 455265552..50fa847b6 100644 --- a/src/models/nullable.rs +++ b/src/models/nullable.rs @@ -5,6 +5,7 @@ /// /// Similarly, not all gateway events provide all information in objects. Again, user profiles /// are notable in that biographies are typically excluded in events to save on bandwidth. +#[must_use = "This enum is used to represent nullable values, and should be used as such"] #[derive(Default, Debug, Clone, Copy, Hash)] #[repr(u8)] pub enum Nullable { @@ -79,6 +80,7 @@ impl Nullable { } /// Converts `Nullable` to `Nullable<&T>`. + #[inline] pub fn as_ref(&self) -> Nullable<&T> { match self { Nullable::Some(value) => Nullable::Some(value), @@ -87,10 +89,20 @@ impl Nullable { } } + /// Converts `Nullable` to `Nullable<&mut T>`. + #[inline] + pub fn as_mut(&mut self) -> Nullable<&mut T> { + match self { + Nullable::Some(value) => Nullable::Some(value), + Nullable::Null => Nullable::Null, + Nullable::Undefined => Nullable::Undefined, + } + } + /// Maps an inner `Some` value to a different value, using `Into`. /// /// Equivalent to `.map(Into::into)`. - pub fn map_into(self) -> Nullable + pub fn convert(self) -> Nullable where T: Into, { diff --git a/src/models/permission.rs b/src/models/permission.rs index 755480d2b..0c199d7bc 100644 --- a/src/models/permission.rs +++ b/src/models/permission.rs @@ -211,6 +211,7 @@ impl Overwrite { /// /// With debug assertions enabled, this function will panic if the IDs do not match. #[inline] + #[must_use] pub const fn combine(&self, other: Self) -> Overwrite { //debug_assert_eq!(self.id, other.id); #[cfg(debug_assertions)] @@ -229,6 +230,7 @@ impl Overwrite { /// /// Equivalent to `(base & !deny) | allow`. #[inline] + #[must_use] pub const fn apply(&self, base: Permissions) -> Permissions { // self.allow(base & !self.deny) | self.allow base.difference(self.deny).union(self.allow) @@ -238,12 +240,14 @@ impl Overwrite { impl Permissions { /// Constructs a new `Permissions` from two `i64` values. #[inline(always)] + #[must_use] pub const fn from_i64(low: i64, high: i64) -> Self { Permissions::from_bits_truncate(low as u64 as u128 | ((high as u64 as u128) << 64)) } /// Constructs a new `Permissions` from two `Option` values, defaulting to `0` if `None` on either. #[inline(always)] + #[must_use] pub const fn from_i64_opt(low: Option, high: Option) -> Self { // TODO: Replace with `.unwrap_or(0)` when that's const-stable Permissions::from_i64( @@ -260,6 +264,7 @@ impl Permissions { /// Converts the `Permissions` into two `i64` values. #[inline(always)] + #[must_use] pub const fn to_i64(self) -> [i64; 2] { let bits = self.bits(); let low = bits as u64 as i64; @@ -268,11 +273,14 @@ impl Permissions { } /// Returns `true` if the permissions contain the `ADMINISTRATOR` permission. + #[inline(always)] + #[must_use] pub const fn is_admin(self) -> bool { self.contains(Permissions::ADMINISTRATOR) } /// Takes cerrtain flags into account and normalizes the permissions to obey them. + #[must_use] pub const fn normalize(self) -> Self { if self.contains(Permissions::DEFAULT_ONLY) { return Permissions::DEFAULT; @@ -286,6 +294,7 @@ impl Permissions { } /// Computes the final permissions for a user in a room given the overwrites and roles. + #[must_use] pub fn compute_overwrites(mut self, overwrites: &[Overwrite], user_roles: &[RoleId], user_id: UserId) -> Permissions { if self.contains(Permissions::ADMINISTRATOR) { return Permissions::all(); diff --git a/src/models/presence.rs b/src/models/presence.rs index 3fe43c4ec..d389fea34 100644 --- a/src/models/presence.rs +++ b/src/models/presence.rs @@ -18,6 +18,7 @@ impl_serde_for_bitflags!(UserPresenceFlags); impl_schema_for_bitflags!(UserPresenceFlags); impl UserPresenceFlags { + #[must_use] pub const fn from_bits_truncate_public(bits: i16) -> Self { Self::from_bits_truncate(bits).difference(Self::INVISIBLE) } @@ -43,6 +44,7 @@ pub struct UserPresence { } impl UserPresence { + #[must_use] pub const fn new(flags: UserPresenceFlags) -> Self { UserPresence { flags, @@ -52,6 +54,7 @@ impl UserPresence { } } + #[must_use] pub fn with_activty(mut self, activity: Option) -> Self { self.activity = activity; self diff --git a/src/models/role.rs b/src/models/role.rs index 5e9b7ec20..85c914a1d 100644 --- a/src/models/role.rs +++ b/src/models/role.rs @@ -33,10 +33,12 @@ pub struct Role { } impl Role { + #[must_use] pub fn is_mentionable(&self) -> bool { self.flags.contains(RoleFlags::MENTIONABLE) } + #[must_use] pub fn is_admin(&self) -> bool { self.permissions.contains(Permissions::ADMINISTRATOR) } diff --git a/src/models/room.rs b/src/models/room.rs index 61afc6f13..70ace51a4 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -30,6 +30,7 @@ impl_schema_for_bitflags!(RoomFlags); impl_sql_for_bitflags!(RoomFlags); impl RoomFlags { + #[must_use] pub fn kind(self) -> RoomKind { // all rooms derive from the text room, so basic queries // will still function if the SDK is not updated as it should diff --git a/src/models/user/mod.rs b/src/models/user/mod.rs index ed827f147..b47287c22 100644 --- a/src/models/user/mod.rs +++ b/src/models/user/mod.rs @@ -85,10 +85,12 @@ pub enum ElevationLevel { impl UserFlags { #[inline] + #[must_use] pub const fn from_bits_truncate_public(bits: i32) -> Self { Self::from_bits_truncate(bits).difference(Self::PRIVATE_FLAGS) } + #[must_use] pub fn elevation(self) -> ElevationLevel { match (self & Self::ELEVATION).bits() >> 6 { 1 => ElevationLevel::Bot, @@ -98,14 +100,17 @@ impl UserFlags { } } + #[must_use] pub const fn with_elevation(self, ev: ElevationLevel) -> Self { self.difference(Self::ELEVATION).union(Self::from_bits_truncate(((ev as u8) as i32) << 6)) } + #[must_use] pub fn premium_level(self) -> u8 { ((self & Self::PREMIUM).bits() >> 9) as u8 } + #[must_use] pub fn extra_storage_tier(self) -> u8 { ((self & Self::EXTRA_STORAGE).bits() >> 13) as u8 } @@ -180,14 +185,20 @@ pub struct UserProfile { } impl UserProfile { + #[inline] + #[must_use] pub fn roundedness(&self) -> f32 { (self.bits & UserProfileBits::AVATAR_ROUNDNESS).bits() as f32 / 127.0 } + #[inline] + #[must_use] pub fn override_color(&self) -> bool { self.bits.contains(UserProfileBits::OVERRIDE_COLOR) } + #[inline] + #[must_use] pub fn color(&self) -> u32 { self.bits.bits() as u32 >> 8 } diff --git a/src/models/util/fixed_str.rs b/src/models/util/fixed_str.rs index 576c4ddc9..f0fe9f55d 100644 --- a/src/models/util/fixed_str.rs +++ b/src/models/util/fixed_str.rs @@ -4,6 +4,7 @@ use core::fmt; /// Fixed-size String that can *only* be a given length, no more or less, exactly N bytes #[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[must_use = "Not using the FixedStr is wasteful"] #[repr(transparent)] pub struct FixedStr { data: [u8; N], @@ -116,6 +117,7 @@ impl FixedStr { /// Construct a new [FixedStr] from a [`&str`](str) if the length is correct. #[inline] + #[must_use] pub const fn try_from(s: &str) -> Option> { if s.len() != N { return None; @@ -125,6 +127,7 @@ impl FixedStr { } #[inline(always)] + #[must_use] pub fn as_str(&self) -> &str { self.as_ref() } diff --git a/src/models/util/thin_str.rs b/src/models/util/thin_str.rs index 274a38224..60cb4da5e 100644 --- a/src/models/util/thin_str.rs +++ b/src/models/util/thin_str.rs @@ -6,6 +6,7 @@ use core::{error, fmt, ops}; use thin_vec::ThinVec; #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[must_use = "Not using the ThinString is wasteful"] #[repr(transparent)] pub struct ThinString(ThinVec); @@ -16,14 +17,17 @@ pub struct FromUtf8Error { } impl FromUtf8Error { + #[must_use] pub fn as_bytes(&self) -> &[u8] { &self.bytes } + #[must_use] pub fn into_bytes(self) -> ThinVec { self.bytes } + #[must_use] pub fn utf8_error(&self) -> &core::str::Utf8Error { &self.error } @@ -66,10 +70,12 @@ impl ThinString { } #[inline(always)] + #[must_use] pub fn as_str(&self) -> &str { unsafe { core::str::from_utf8_unchecked(&self.0) } } + #[must_use] pub fn as_bytes(&self) -> &[u8] { &self.0 } @@ -101,6 +107,7 @@ impl ThinString { self.0.shrink_to_fit(); } + #[must_use] pub fn into_bytes(self) -> ThinVec { self.0 } @@ -109,10 +116,12 @@ impl ThinString { &mut self.0 } + #[must_use] pub fn len(&self) -> usize { self.0.len() } + #[must_use] pub fn is_empty(&self) -> bool { self.0.is_empty() }