From 1f93a2ff1843008a655e469a48979fcd7b39ef1a Mon Sep 17 00:00:00 2001 From: Marcos Henrich Date: Wed, 31 May 2023 14:01:47 +0100 Subject: [PATCH] Adds fully qualified paths for methods. (#4579) ## Description With the changes in this PR it is now possible to qualify a trait to disambiguate the method to be used, such as `::method()`. Fixes #4383 Fixes #4025 ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [ ] 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. --------- Co-authored-by: IGI-111 --- .../language/parsed/expression/method_name.rs | 10 +- .../src/language/parsed/expression/mod.rs | 9 + .../ast_node/expression/typed_expression.rs | 42 +++- .../typed_expression/method_application.rs | 39 ++++ .../semantic_analysis/namespace/namespace.rs | 204 +++++++++++++++--- .../semantic_analysis/node_dependencies.rs | 1 + .../to_parsed_lang/convert_parse_tree.rs | 41 +++- sway-error/src/convert_parse_tree_error.rs | 5 + sway-error/src/error.rs | 14 ++ sway-lsp/src/traverse/parsed_tree.rs | 9 + .../trait_method_ambiguous/Forc.lock | 3 + .../trait_method_ambiguous/Forc.toml | 6 + .../trait_method_ambiguous/src/main.sw | 31 +++ .../trait_method_ambiguous/test.toml | 14 ++ .../trait_method_generic_ambiguous/Forc.lock | 3 + .../trait_method_generic_ambiguous/Forc.toml | 6 + .../src/main.sw | 29 +++ .../trait_method_generic_ambiguous/test.toml | 9 + .../Forc.lock | 13 ++ .../Forc.toml | 8 + .../json_abi_oracle.json | 25 +++ .../src/main.sw | 33 +++ .../test.toml | 4 + .../trait_method_generic_qualified/Forc.lock | 13 ++ .../trait_method_generic_qualified/Forc.toml | 8 + .../json_abi_oracle.json | 25 +++ .../src/main.sw | 33 +++ .../trait_method_generic_qualified/test.toml | 4 + .../language/trait_method_qualified/Forc.lock | 13 ++ .../language/trait_method_qualified/Forc.toml | 8 + .../json_abi_oracle.json | 25 +++ .../trait_method_qualified/src/main.sw | 37 ++++ .../language/trait_method_qualified/test.toml | 3 + 33 files changed, 695 insertions(+), 32 deletions(-) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/json_abi_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/json_abi_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/json_abi_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/test.toml diff --git a/sway-core/src/language/parsed/expression/method_name.rs b/sway-core/src/language/parsed/expression/method_name.rs index d3609250ddd..a50f6cdd5ac 100644 --- a/sway-core/src/language/parsed/expression/method_name.rs +++ b/sway-core/src/language/parsed/expression/method_name.rs @@ -1,6 +1,6 @@ use crate::language::CallPath; use crate::type_system::TypeBinding; -use crate::{Ident, TypeInfo}; +use crate::{Ident, TypeArgument, TypeInfo}; #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] @@ -20,6 +20,13 @@ pub enum MethodName { /// used for things like core::ops::add(a, b). /// in this case, the first argument determines the type to look for FromTrait { call_path: CallPath }, + /// Represents a method lookup with a fully qualified path. + /// like ::method() + FromQualifiedPathRoot { + ty: TypeArgument, + as_trait: TypeInfo, + method_name: Ident, + }, } impl MethodName { @@ -29,6 +36,7 @@ impl MethodName { MethodName::FromType { method_name, .. } => method_name.clone(), MethodName::FromTrait { call_path, .. } => call_path.suffix.clone(), MethodName::FromModule { method_name, .. } => method_name.clone(), + MethodName::FromQualifiedPathRoot { method_name, .. } => method_name.clone(), } } } diff --git a/sway-core/src/language/parsed/expression/mod.rs b/sway-core/src/language/parsed/expression/mod.rs index 4b789ec66c8..e63ac3ce02a 100644 --- a/sway-core/src/language/parsed/expression/mod.rs +++ b/sway-core/src/language/parsed/expression/mod.rs @@ -1,6 +1,7 @@ use crate::{ language::{parsed::CodeBlock, *}, type_system::TypeBinding, + TypeArgument, TypeInfo, }; use sway_types::{ident::Ident, Span, Spanned}; @@ -103,8 +104,16 @@ impl Spanned for AmbiguousSuffix { } } +#[derive(Debug, Clone)] +pub struct QualifiedPathRootTypes { + pub ty: TypeArgument, + pub as_trait: TypeInfo, + pub as_trait_span: Span, +} + #[derive(Debug, Clone)] pub struct AmbiguousPathExpression { + pub qualified_path_root: Option, pub call_path_binding: TypeBinding>, pub args: Vec, } 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 3e1000a3be0..196cfdb97c7 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 @@ -228,8 +228,15 @@ impl ty::TyExpression { let AmbiguousPathExpression { call_path_binding, args, + qualified_path_root, } = *e; - Self::type_check_ambiguous_path(ctx.by_ref(), call_path_binding, span, args) + Self::type_check_ambiguous_path( + ctx.by_ref(), + call_path_binding, + span, + args, + qualified_path_root, + ) } ExpressionKind::DelineatedPath(delineated_path_expression) => { let DelineatedPathExpression { @@ -1036,9 +1043,42 @@ impl ty::TyExpression { }: TypeBinding>, span: Span, args: Vec, + qualified_path_root: Option, ) -> CompileResult { let decl_engine = ctx.decl_engine; + if let Some(QualifiedPathRootTypes { ty, as_trait, .. }) = qualified_path_root { + if !prefixes.is_empty() || before.is_some() { + return err( + vec![], + vec![ + ConvertParseTreeError::UnexpectedCallPathPrefixAfterQualifiedRoot { + span: path_span, + } + .into(), + ], + ); + } + + let method_name_binding = TypeBinding { + inner: MethodName::FromQualifiedPathRoot { + ty, + as_trait, + method_name: suffix, + }, + type_arguments, + span: path_span, + }; + + return type_check_method_application( + ctx.by_ref(), + method_name_binding, + Vec::new(), + args, + span, + ); + } + // is it a singleton? let before = if let Some(b) = before { b diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 98235578d2b..24638b51619 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -270,6 +270,11 @@ pub(crate) fn type_check_method_application( is_absolute: false, }, MethodName::FromTrait { call_path } => call_path, + MethodName::FromQualifiedPathRoot { method_name, .. } => CallPath { + prefixes: vec![], + suffix: method_name, + is_absolute: false, + }, }; // build the function selector @@ -463,7 +468,9 @@ pub(crate) fn resolve_method_name( &type_info_prefix, method_name, ctx.self_type(), + ctx.type_annotation(), &arguments, + None, engines, ), return err(warnings, errors), @@ -490,7 +497,9 @@ pub(crate) fn resolve_method_name( &module_path, &call_path.suffix, ctx.self_type(), + ctx.type_annotation(), &arguments, + None, engines, ), return err(warnings, errors), @@ -517,7 +526,9 @@ pub(crate) fn resolve_method_name( &module_path, method_name, ctx.self_type(), + ctx.type_annotation(), &arguments, + None, engines, ), return err(warnings, errors), @@ -525,6 +536,34 @@ pub(crate) fn resolve_method_name( errors ); + (decl_ref, type_id) + } + MethodName::FromQualifiedPathRoot { + ty, + as_trait, + method_name, + } => { + // type check the call path + let type_id = ty.type_id; + let type_info_prefix = vec![]; + + // find the method + let decl_ref = check!( + ctx.namespace.find_method_for_type( + type_id, + &type_info_prefix, + method_name, + ctx.self_type(), + ctx.type_annotation(), + &arguments, + Some(as_trait.clone()), + engines + ), + return err(warnings, errors), + warnings, + errors + ); + (decl_ref, type_id) } }; diff --git a/sway-core/src/semantic_analysis/namespace/namespace.rs b/sway-core/src/semantic_analysis/namespace/namespace.rs index 7e47d2f271b..002ca67ae61 100644 --- a/sway-core/src/semantic_analysis/namespace/namespace.rs +++ b/sway-core/src/semantic_analysis/namespace/namespace.rs @@ -2,7 +2,10 @@ use crate::{ decl_engine::{DeclRefConstant, DeclRefFunction}, engine_threading::*, error::*, - language::{ty, CallPath, Visibility}, + language::{ + ty::{self}, + CallPath, Visibility, + }, type_system::*, CompileResult, Ident, }; @@ -15,7 +18,7 @@ use super::{ use sway_error::error::CompileError; use sway_types::{span::Span, Spanned}; -use std::{cmp::Ordering, collections::VecDeque}; +use std::collections::{HashMap, VecDeque}; /// The set of items that represent the namespace context passed throughout type checking. #[derive(Clone, Debug)] @@ -256,7 +259,9 @@ impl Namespace { method_prefix: &Path, method_name: &Ident, self_type: TypeId, + annotation_type: TypeId, args_buf: &VecDeque, + as_trait: Option, engines: Engines<'_>, ) -> CompileResult { let mut warnings = vec![]; @@ -280,38 +285,174 @@ impl Namespace { }) .collect::>(); - let matching_method_decl_ref = match matching_method_decl_refs.len().cmp(&1) { - Ordering::Equal => matching_method_decl_refs.get(0).cloned(), - Ordering::Greater => { - // Case where multiple methods exist with the same name - // This is the case of https://github.com/FuelLabs/sway/issues/3633 - // where multiple generic trait impls use the same method name but with different parameter types - let mut maybe_method_decl_ref: Option = None; - for decl_ref in matching_method_decl_refs.clone().into_iter() { - let method = decl_engine.get_function(&decl_ref); - if method.parameters.len() == args_buf.len() - && !method.parameters.iter().zip(args_buf.iter()).any(|(p, a)| { - !are_equal_minus_dynamic_types( - engines, - p.type_argument.type_id, - a.return_type, - ) - }) + let mut qualified_call_path = None; + let matching_method_decl_ref = { + // Case where multiple methods exist with the same name + // This is the case of https://github.com/FuelLabs/sway/issues/3633 + // where multiple generic trait impls use the same method name but with different parameter types + let mut maybe_method_decl_refs: Vec = vec![]; + for decl_ref in matching_method_decl_refs.clone().into_iter() { + let method = decl_engine.get_function(&decl_ref); + if method.parameters.len() == args_buf.len() + && !method.parameters.iter().zip(args_buf.iter()).any(|(p, a)| { + !are_equal_minus_dynamic_types( + engines, + p.type_argument.type_id, + a.return_type, + ) + }) + && (matches!(type_engine.get(annotation_type), TypeInfo::Unknown) + || are_equal_minus_dynamic_types( + engines, + annotation_type, + method.return_type.type_id, + )) + { + maybe_method_decl_refs.push(decl_ref); + } + } + + if !maybe_method_decl_refs.is_empty() { + let mut trait_methods = + HashMap::<(CallPath, Vec>), DeclRefFunction>::new(); + let mut impl_self_method = None; + for method_ref in maybe_method_decl_refs.clone() { + let method = decl_engine.get_function(&method_ref); + if let Some(ty::TyDecl::ImplTrait(impl_trait)) = + method.implementing_type.clone() { - maybe_method_decl_ref = Some(decl_ref); - break; + let trait_decl = decl_engine.get_impl_trait(&impl_trait.decl_id); + if let Some(TypeInfo::Custom { + call_path, + type_arguments, + }) = as_trait.clone() + { + qualified_call_path = Some(call_path.clone()); + // When `>::method()` is used we only add methods to `trait_methods` that + // originate from the qualified trait. + if trait_decl.trait_name == call_path { + let mut params_equal = true; + if let Some(params) = type_arguments { + if params.len() != trait_decl.trait_type_arguments.len() { + params_equal = false; + } else { + for (p1, p2) in params + .iter() + .zip(trait_decl.trait_type_arguments.clone()) + { + let p1_type_id = check!( + self.resolve_type_without_self( + engines, p1.type_id, &p1.span, None + ), + return err(warnings, errors), + warnings, + errors + ); + let p2_type_id = check!( + self.resolve_type_without_self( + engines, p2.type_id, &p2.span, None + ), + return err(warnings, errors), + warnings, + errors + ); + if !are_equal_minus_dynamic_types( + engines, p1_type_id, p2_type_id, + ) { + params_equal = false; + break; + } + } + } + } + if params_equal { + trait_methods.insert( + ( + trait_decl.trait_name, + trait_decl + .trait_type_arguments + .iter() + .cloned() + .map(|a| engines.help_out(a)) + .collect::>(), + ), + method_ref.clone(), + ); + } + } + } else { + trait_methods.insert( + ( + trait_decl.trait_name, + trait_decl + .trait_type_arguments + .iter() + .cloned() + .map(|a| engines.help_out(a)) + .collect::>(), + ), + method_ref.clone(), + ); + } + if trait_decl.trait_decl_ref.is_none() { + impl_self_method = Some(method_ref); + } } } - if let Some(matching_method_decl_ref) = maybe_method_decl_ref { - // In case one or more methods match the parameter types we return the first match. - Some(matching_method_decl_ref) + + if trait_methods.len() == 1 { + trait_methods.values().next().cloned() + } else if trait_methods.len() > 1 { + if impl_self_method.is_some() { + // In case we have trait methods and a impl self method we use the impl self method. + impl_self_method + } else { + fn to_string( + trait_name: CallPath, + trait_type_args: Vec>, + ) -> String { + format!( + "{}{}", + trait_name.suffix, + if trait_type_args.is_empty() { + String::new() + } else { + format!( + "<{}>", + trait_type_args + .iter() + .map(|type_arg| type_arg.to_string()) + .collect::>() + .join(", ") + ) + } + ) + } + let mut trait_strings = trait_methods + .keys() + .map(|t| to_string(t.0.clone(), t.1.clone())) + .collect::>(); + // Sort so the output of the error is always the same. + trait_strings.sort(); + errors.push(CompileError::MultipleApplicableItemsInScope { + method_name: method_name.as_str().to_string(), + type_name: engines.help_out(type_id).to_string(), + as_traits: trait_strings, + span: method_name.span(), + }); + return err(warnings, errors); + } + } else if qualified_call_path.is_some() { + // When we use a qualified path the expected method should be in trait_methods. + None } else { - // When we can't match any method with parameter types we still return the first method found - // This was the behavior before introducing the parameter type matching - matching_method_decl_refs.get(0).cloned() + maybe_method_decl_refs.get(0).cloned() } + } else { + // When we can't match any method with parameter types we still return the first method found + // This was the behavior before introducing the parameter type matching + matching_method_decl_refs.get(0).cloned() } - Ordering::Less => None, }; if let Some(method_decl_ref) = matching_method_decl_ref { @@ -323,9 +464,14 @@ impl Namespace { .map(|x| type_engine.get(x.return_type)) .eq(&Some(TypeInfo::ErrorRecovery), engines) { + let type_name = if let Some(call_path) = qualified_call_path { + format!("{} as {}", engines.help_out(type_id), call_path) + } else { + engines.help_out(type_id).to_string() + }; errors.push(CompileError::MethodNotFound { method_name: method_name.clone(), - type_name: engines.help_out(type_id).to_string(), + type_name, span: method_name.span(), }); } diff --git a/sway-core/src/semantic_analysis/node_dependencies.rs b/sway-core/src/semantic_analysis/node_dependencies.rs index f7210babc3d..3f6fd3cf2e9 100644 --- a/sway-core/src/semantic_analysis/node_dependencies.rs +++ b/sway-core/src/semantic_analysis/node_dependencies.rs @@ -516,6 +516,7 @@ impl Dependencies { let AmbiguousPathExpression { call_path_binding, args, + qualified_path_root: _, } = &**e; let mut this = self; if call_path_binding.inner.prefixes.is_empty() { diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index 718fbd0ec42..878589c256c 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -1610,7 +1610,8 @@ fn expr_func_app_to_expression_kind( } }; - let is_absolute = path_root_opt_to_bool(context, handler, root_opt)?; + let (is_absolute, qualified_path_root) = + path_root_opt_to_bool_and_qualified_path_root(context, handler, engines, root_opt)?; let convert_ty_args = |context: &mut Context, generics_opt: Option<(_, GenericArgs)>| { Ok(match generics_opt { @@ -1695,6 +1696,7 @@ fn expr_func_app_to_expression_kind( AmbiguousPathExpression { args: arguments, call_path_binding, + qualified_path_root, }, ))); } @@ -1726,6 +1728,7 @@ fn expr_func_app_to_expression_kind( AmbiguousPathExpression { args: arguments, call_path_binding, + qualified_path_root, }, ))) } @@ -2708,6 +2711,42 @@ fn path_root_opt_to_bool( }) } +fn path_root_opt_to_bool_and_qualified_path_root( + context: &mut Context, + handler: &Handler, + engines: Engines<'_>, + root_opt: Option<(Option>, DoubleColonToken)>, +) -> Result<(bool, Option), ErrorEmitted> { + Ok(match root_opt { + None => (false, None), + Some((None, _)) => (true, None), + Some(( + Some(AngleBrackets { + open_angle_bracket_token: _, + inner: QualifiedPathRoot { ty, as_trait }, + close_angle_bracket_token: _, + }), + _, + )) => ( + false, + if let Some((_, path_type)) = as_trait { + Some(QualifiedPathRootTypes { + ty: ty_to_type_argument(context, handler, engines, *ty)?, + as_trait: path_type_to_type_info( + context, + handler, + engines, + *path_type.clone(), + )?, + as_trait_span: path_type.span(), + }) + } else { + None + }, + ), + }) +} + fn literal_to_literal( _context: &mut Context, handler: &Handler, diff --git a/sway-error/src/convert_parse_tree_error.rs b/sway-error/src/convert_parse_tree_error.rs index dd3b76c8aa8..19d24d82bde 100644 --- a/sway-error/src/convert_parse_tree_error.rs +++ b/sway-error/src/convert_parse_tree_error.rs @@ -121,6 +121,8 @@ pub enum ConvertParseTreeError { InvalidCfgProgramTypeArgValue { span: Span, value: String }, #[error("Expected a value for the program_type argument")] ExpectedCfgProgramTypeArgValue { span: Span }, + #[error("Unexpected call path segments between qualified root and method name.")] + UnexpectedCallPathPrefixAfterQualifiedRoot { span: Span }, } impl Spanned for ConvertParseTreeError { @@ -185,6 +187,9 @@ impl Spanned for ConvertParseTreeError { ConvertParseTreeError::ExpectedCfgTargetArgValue { span } => span.clone(), ConvertParseTreeError::InvalidCfgProgramTypeArgValue { span, .. } => span.clone(), ConvertParseTreeError::ExpectedCfgProgramTypeArgValue { span } => span.clone(), + ConvertParseTreeError::UnexpectedCallPathPrefixAfterQualifiedRoot { span } => { + span.clone() + } } } } diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index 19f42689f4c..d59986f9cb7 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -639,6 +639,19 @@ pub enum CompileError { ConfigurableInLibrary { span: Span }, #[error("The name `{name}` is defined multiple times")] NameDefinedMultipleTimes { name: String, span: Span }, + #[error("Multiple applicable items in scope. {}", { + let mut candidates = "".to_string(); + for (index, as_trait) in as_traits.iter().enumerate() { + candidates = format!("{candidates}\n Disambiguate the associated function for candidate #{index}\n <{type_name} as {as_trait}>::{method_name}("); + } + candidates + })] + MultipleApplicableItemsInScope { + span: Span, + type_name: String, + method_name: String, + as_traits: Vec, + }, } impl std::convert::From for CompileError { @@ -809,6 +822,7 @@ impl Spanned for CompileError { TraitImplPayabilityMismatch { span, .. } => span.clone(), ConfigurableInLibrary { span } => span.clone(), NameDefinedMultipleTimes { span, .. } => span.clone(), + MultipleApplicableItemsInScope { span, .. } => span.clone(), } } } diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index 61b289b12b7..5cfe5be40dc 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -405,6 +405,7 @@ impl Parse for AmbiguousPathExpression { let AmbiguousPathExpression { call_path_binding, args, + qualified_path_root, } = self; for ident in call_path_binding.inner.prefixes.iter().chain( call_path_binding @@ -434,6 +435,14 @@ impl Parse for AmbiguousPathExpression { type_arg.parse(ctx); }); args.iter().for_each(|arg| arg.parse(ctx)); + if let Some(qualified_path_root) = qualified_path_root { + qualified_path_root.ty.parse(ctx); + collect_type_info_token( + ctx, + &qualified_path_root.as_trait, + Some(&qualified_path_root.as_trait_span), + ); + } } } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.lock new file mode 100644 index 00000000000..4e170a02053 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'trait_method_ambiguous' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.toml new file mode 100644 index 00000000000..fd710e7bea8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "trait_method_ambiguous" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/src/main.sw new file mode 100644 index 00000000000..b223433dc91 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/src/main.sw @@ -0,0 +1,31 @@ +script; + +struct S {} + +struct S2 {} + +trait MySuperTrait { + fn method(); +} + +trait MyTrait : MySuperTrait { + fn method(); +} + +impl MySuperTrait for S { + fn method() { } +} + +impl MyTrait for S { + fn method() { } +} + +fn main() { + ::asd::method(); + + + ::method(); + + + S::method(); // ambiguous method call here +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/test.toml new file mode 100644 index 00000000000..c315e4d754b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_ambiguous/test.toml @@ -0,0 +1,14 @@ +category = "fail" + +# check: $()::asd::method(); +# nextln: $()Unexpected call path segments between qualified root and method name. + +# check: $()::method(); +# nextln: $()No method named "method" found for type "S as S2". + +# check: $()S::method(); // ambiguous method call here +# nextln: $()Multiple applicable items in scope. +# nextln: $()Disambiguate the associated function for candidate #0 +# nextln: $()::method( +# nextln: $()Disambiguate the associated function for candidate #1 +# nextln: $()::method( diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.lock new file mode 100644 index 00000000000..ff1ee4a1bfb --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'trait_method_generic_ambiguous' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.toml new file mode 100644 index 00000000000..5c0f8d80a0b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "trait_method_generic_ambiguous" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/src/main.sw new file mode 100644 index 00000000000..b67ac9d898b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/src/main.sw @@ -0,0 +1,29 @@ +script; + +trait Trait { + fn method(self) -> u64; +} + +struct S1 { + s1: u64 +} + +struct S2 { + s2: u64 +} + +impl Trait for u64 { + fn method(self) -> u64 { + 1 + } +} + +impl Trait for u64 { + fn method(self) -> u64 { + 2 + } +} + +fn main() { + let _v1 = 42.method(); +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/test.toml new file mode 100644 index 00000000000..43ead71bcc3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_method_generic_ambiguous/test.toml @@ -0,0 +1,9 @@ +category = "fail" + +# check: $()let _v1 = 42.method(); +# nextln: $()Multiple applicable items in scope. +# nextln: $()Disambiguate the associated function for candidate #0 +# nextln: $()>::method( +# nextln: $()Disambiguate the associated function for candidate #1 +# nextln: $()>::method( + diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.lock new file mode 100644 index 00000000000..397bdd390df --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = 'core' +source = 'path+from-root-6A090211C259E511' + +[[package]] +name = 'std' +source = 'path+from-root-6A090211C259E511' +dependencies = ['core'] + +[[package]] +name = 'trait_method_ascription_disambiguate' +source = 'member' +dependencies = ['std'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.toml new file mode 100644 index 00000000000..285af3d230c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "trait_method_ascription_disambiguate" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/json_abi_oracle.json new file mode 100644 index 00000000000..03b2f150939 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/json_abi_oracle.json @@ -0,0 +1,25 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": null, + "type": "bool", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/src/main.sw new file mode 100644 index 00000000000..647da9e879f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/src/main.sw @@ -0,0 +1,33 @@ +script; + +trait From2 { + fn into2(self) -> T; +} + +struct S1 { + s1: u64 +} + +struct S2 { + s2: u64 +} + +impl From2 for u64 { + fn into2(self) -> S1 { + S1{s1: self} + } +} + +impl From2 for u64 { + fn into2(self) -> S2 { + S2{s2: self} + } +} + +fn main() -> bool { + let _s1: S1 = 42.into2(); + + let _s2: S2 = 42.into2(); + + true +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/test.toml new file mode 100644 index 00000000000..57fc995da49 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_ascription_disambiguate/test.toml @@ -0,0 +1,4 @@ +category = "run" +expected_result = { action = "return", value = 1 } +validate_abi = true +expected_warnings = 2 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.lock new file mode 100644 index 00000000000..00b972ca726 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = 'core' +source = 'path+from-root-75394A513C1CBC16' + +[[package]] +name = 'std' +source = 'path+from-root-75394A513C1CBC16' +dependencies = ['core'] + +[[package]] +name = 'trait_method_generic_qualified' +source = 'member' +dependencies = ['std'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.toml new file mode 100644 index 00000000000..be4ae677a6c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "trait_method_generic_qualified" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/json_abi_oracle.json new file mode 100644 index 00000000000..03b2f150939 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/json_abi_oracle.json @@ -0,0 +1,25 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": null, + "type": "bool", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/src/main.sw new file mode 100644 index 00000000000..55ee6dee706 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/src/main.sw @@ -0,0 +1,33 @@ +script; + +trait From2 { + fn into2(self) -> T; +} + +struct S1 { + s1: u64 +} + +struct S2 { + s2: u64 +} + +impl From2 for u64 { + fn into2(self) -> S1 { + S1{s1: self} + } +} + +impl From2 for u64 { + fn into2(self) -> S2 { + S2{s2: self} + } +} + +fn main() -> bool { + let _s1: S1 = >::into2(42); + + let _s2: S2 = >::into2(42); + + true +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/test.toml new file mode 100644 index 00000000000..57fc995da49 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_generic_qualified/test.toml @@ -0,0 +1,4 @@ +category = "run" +expected_result = { action = "return", value = 1 } +validate_abi = true +expected_warnings = 2 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.lock new file mode 100644 index 00000000000..abeaf704931 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = 'core' +source = 'path+from-root-BFB25A0DE453D24E' + +[[package]] +name = 'std' +source = 'path+from-root-BFB25A0DE453D24E' +dependencies = ['core'] + +[[package]] +name = 'trait_method_qualified' +source = 'member' +dependencies = ['std'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.toml new file mode 100644 index 00000000000..77aedee1026 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "trait_method_qualified" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/json_abi_oracle.json new file mode 100644 index 00000000000..03b2f150939 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/json_abi_oracle.json @@ -0,0 +1,25 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": null, + "type": "bool", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/src/main.sw new file mode 100644 index 00000000000..2867c1c5f3d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/src/main.sw @@ -0,0 +1,37 @@ +script; + +struct S {} + +impl S { + fn method() -> u64 { + 1 + } +} + +trait MySuperTrait { + fn method() -> u64; +} + +trait MyTrait : MySuperTrait { + fn method() -> u64; +} + +impl MySuperTrait for S { + fn method() -> u64 { + 2 + } +} + +impl MyTrait for S { + fn method() -> u64 { + 3 + } +} + +fn main() -> bool { + assert(S::method() == 1); + assert(::method() == 2); + assert(::method() == 3); + + true +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/test.toml new file mode 100644 index 00000000000..ace9e6f3186 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/trait_method_qualified/test.toml @@ -0,0 +1,3 @@ +category = "run" +expected_result = { action = "return", value = 1 } +validate_abi = true