slang_solidity 1.3.5

A modular set of compiler APIs empowering the next generation of Solidity code analysis and developer tooling. Written in Rust and distributed in multiple languages.
Documentation
use super::Pass;
use crate::backend::binder::{Definition, ParameterDefinition, Scope, Typing};
use crate::backend::types::{FunctionType, Type, TypeId};
use crate::cst::NodeId;

/// Disambiguation functions that require typing (aka overload resolution)
impl Pass<'_> {
    fn get_function_definition_parameters(
        &self,
        definition_id: Option<NodeId>,
    ) -> Option<&[ParameterDefinition]> {
        let Some(Definition::Function(function_definition)) =
            self.binder.find_definition_by_id(definition_id?)
        else {
            return None;
        };

        let Scope::Parameters(parameters_scope) = self
            .binder
            .get_scope_by_id(function_definition.parameters_scope_id)
        else {
            unreachable!("incorrect scope kind, expected parameters");
        };
        Some(&parameters_scope.parameters)
    }

    pub(super) fn lookup_function_matching_positional_arguments<'a>(
        &'a self,
        type_ids: &[TypeId],
        argument_typings: &[Typing],
        receiver_type_id: Option<TypeId>,
    ) -> Option<&'a FunctionType> {
        // get types and filter non-function types
        let mut function_types = type_ids.iter().filter_map(|type_id| {
            if let Type::Function(function_type) = self.types.get_type_by_id(*type_id) {
                Some(function_type)
            } else {
                None
            }
        });
        function_types.find(|function_type| {
            let Some(parameters) =
                self.get_function_definition_parameters(function_type.definition_id)
            else {
                return false;
            };
            if let Some(receiver_type_id) = receiver_type_id {
                // we have a receiver type, so either check for an implicit
                // receiver type, or the first parameter type
                // against it and then the rest, if the counts match
                if parameters.len() == argument_typings.len()
                    && function_type.implicit_receiver_type.is_some_and(
                        |implicit_receiver_type_id| {
                            self.types.implicitly_convertible_to(
                                receiver_type_id,
                                implicit_receiver_type_id,
                            )
                        },
                    )
                {
                    // matches "method" functions of a compatible receiver type
                    self.parameters_match_positional_arguments(
                        parameters,
                        argument_typings,
                        function_type.external,
                    )
                } else if parameters.len() == argument_typings.len() + 1
                    && function_type.implicit_receiver_type.is_none()
                    && parameters.first().is_some_and(|parameter| {
                        parameter.type_id.is_some_and(|type_id| {
                            self.types
                                .implicitly_convertible_to(receiver_type_id, type_id)
                        })
                    })
                {
                    // matches attached functions (these can only be
                    // free-functions or library functions) with a compatible
                    // first argument
                    self.parameters_match_positional_arguments(
                        &parameters[1..],
                        argument_typings,
                        function_type.external,
                    )
                } else {
                    false
                }
            } else if parameters.len() == argument_typings.len() {
                // argument count matches, check that all types are implicitly convertible
                self.parameters_match_positional_arguments(
                    parameters,
                    argument_typings,
                    function_type.external,
                )
            } else {
                false
            }
        })
    }

    fn parameters_match_positional_arguments(
        &self,
        parameters: &[ParameterDefinition],
        argument_typings: &[Typing],
        external_call: bool,
    ) -> bool {
        parameters
            .iter()
            .zip(argument_typings)
            .all(|(parameter, argument_typing)| {
                parameter.type_id.is_some_and(|type_id| {
                    self.parameter_type_matches_argument_typing(
                        type_id,
                        argument_typing,
                        external_call,
                    )
                })
            })
    }

    fn parameter_type_matches_argument_typing(
        &self,
        parameter_type: TypeId,
        argument_typing: &Typing,
        external_call: bool,
    ) -> bool {
        match argument_typing {
            Typing::Resolved(type_id) => {
                if external_call {
                    self.types
                        .implicitly_convertible_to_for_external_call(*type_id, parameter_type)
                } else {
                    self.types
                        .implicitly_convertible_to(*type_id, parameter_type)
                }
            }
            Typing::This => self
                .types
                .implicitly_convertible_to(self.types.address(), parameter_type),
            _ => false,
        }
    }

    pub(super) fn lookup_function_matching_named_arguments<'a>(
        &'a self,
        type_ids: &[TypeId],
        argument_typings: &[(String, Typing)],
        receiver_type_id: Option<TypeId>,
    ) -> Option<&'a FunctionType> {
        // get types and filter non-function types
        let mut function_types = type_ids.iter().filter_map(|type_id| {
            if let Type::Function(function_type) = self.types.get_type_by_id(*type_id) {
                Some(function_type)
            } else {
                None
            }
        });
        function_types.find(|function_type| {
            let Some(parameters) =
                self.get_function_definition_parameters(function_type.definition_id)
            else {
                return false;
            };
            if parameters.iter().any(|parameter| parameter.name.is_none()) {
                // function has an unnamed parameter and we cannot use it for
                // matching named arguments
                return false;
            }

            if parameters.len() == argument_typings.len() {
                // argument count matches, check that all types are implicitly convertible
                self.parameters_match_named_arguments(
                    parameters,
                    argument_typings,
                    function_type.external,
                )
            } else if let Some(receiver_type_id) = receiver_type_id {
                // we have a receiver type, so check the first parameter type
                // against it and then the rest, if the counts match
                if parameters.len() == argument_typings.len() + 1
                    && parameters.first().is_some_and(|parameter| {
                        parameter.type_id.is_some_and(|type_id| {
                            self.types
                                .implicitly_convertible_to(receiver_type_id, type_id)
                        })
                    })
                {
                    self.parameters_match_named_arguments(
                        &parameters[1..],
                        argument_typings,
                        function_type.external,
                    )
                } else {
                    false
                }
            } else {
                false
            }
        })
    }

    fn parameters_match_named_arguments(
        &self,
        parameters: &[ParameterDefinition],
        argument_typings: &[(String, Typing)],
        external_call: bool,
    ) -> bool {
        argument_typings
            .iter()
            .all(|(argument_name, argument_typing)| {
                let Some(parameter) = parameters.iter().find(|parameter| {
                    parameter
                        .name
                        .as_ref()
                        .is_some_and(|name| name == argument_name)
                }) else {
                    return false;
                };
                parameter.type_id.is_some_and(|type_id| {
                    self.parameter_type_matches_argument_typing(
                        type_id,
                        argument_typing,
                        external_call,
                    )
                })
            })
    }

    fn get_event_definition_parameters(
        &self,
        definition_id: NodeId,
    ) -> Option<&[ParameterDefinition]> {
        let Some(Definition::Event(event_definition)) =
            self.binder.find_definition_by_id(definition_id)
        else {
            return None;
        };

        let Scope::Parameters(parameters_scope) = self
            .binder
            .get_scope_by_id(event_definition.parameters_scope_id)
        else {
            unreachable!("incorrect scope kind, expected parameters");
        };
        Some(&parameters_scope.parameters)
    }

    pub(super) fn lookup_event_matching_positional_arguments(
        &self,
        definition_ids: &[NodeId],
        argument_typings: &[Typing],
    ) -> Option<NodeId> {
        for definition_id in definition_ids {
            let Some(parameters) = self.get_event_definition_parameters(*definition_id) else {
                continue;
            };
            if parameters.len() == argument_typings.len() {
                // argument count matches, check that all types are implicitly convertible
                if self.parameters_match_positional_arguments(parameters, argument_typings, false) {
                    return Some(*definition_id);
                }
            }
        }
        None
    }

    pub(super) fn lookup_event_matching_named_arguments(
        &self,
        definition_ids: &[NodeId],
        argument_typings: &[(String, Typing)],
    ) -> Option<NodeId> {
        for definition_id in definition_ids {
            let Some(parameters) = self.get_event_definition_parameters(*definition_id) else {
                continue;
            };

            if parameters.iter().any(|parameter| parameter.name.is_none()) {
                // cannot match if any parameter is unnamed
                continue;
            }

            if parameters.len() == argument_typings.len() {
                // argument count matches, check that all types are implicitly convertible
                if self.parameters_match_named_arguments(parameters, argument_typings, false) {
                    return Some(*definition_id);
                }
            }
        }
        None
    }
}