Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait method takes precedence over inherent method on ambiguous numeric type #99405

Open
steffahn opened this issue Jul 18, 2022 · 4 comments
Open
Labels
A-inference Area: Type inference A-trait-system Area: Trait system A-type-system Area: Type system C-discussion Category: Discussion or questions that doesn't represent real issues. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@steffahn
Copy link
Member

steffahn commented Jul 18, 2022

This is arguably quite confusing:

trait Trait {
    fn abs(self) -> Self;
}

impl Trait for i64 {
    fn abs(self) -> Self {
        2 * self
    }
}

fn main() {
    let x = 42;
    println!("{}", x.abs());
    println!("{}", x.abs());
}

Ouput:

84
42

So the first x.abs() call resolves to the trait method, which is then used to influence type inference to make x an i64. With x being i64, the second x.abs() call calls the inherent method.

@rustbot label T-compiler, T-lang, A-traits, A-typesystem

@steffahn steffahn added the C-bug Category: This is a bug. label Jul 18, 2022
@rustbot rustbot added A-trait-system Area: Trait system T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. A-type-system Area: Type system labels Jul 18, 2022
@eddyb
Copy link
Member

eddyb commented Jul 18, 2022

One thing that fell through the tracks was one of my suggestions (to @workingjubilee) regarding a potential complete replacement for inherent impls on builtin types, that still has a lot of the same user-facing properties (i.e. those methods take precedence over methods defined by traits instead of being ambiguous with them).

The advantage is that if iN::abs came from a trait, we can represent <_ as core::num::IntMethods>::abs before inferring which exact type it is, whereas inherent impls require eagerly picking one.

(original motivation was making 2.0.sqrt() work, though 42.abs() is basically the same thing)

@workingjubilee
Copy link
Member

Hello!

It didn't quite fall through the tracks so much as I wound up kinda busy at an inopportune time. I hope to take a closer look at this soon. 👀

@ikey4u
Copy link

ikey4u commented Feb 23, 2024

Dump the source into LLVM IR using command rustc --emit=asm,llvm-bc,llvm-ir src/main.rs, we got following code

define i32 @main(i32 %0, ptr %1) unnamed_addr #6 {
top:
  %2 = sext i32 %0 to i64
; call std::rt::lang_start
  %3 = call i64 @_ZN3std2rt10lang_start17h83abcc9980350894E(ptr @_ZN4main4main17hf586f8163becbb21E, i64 %2, ptr %1, i8 0)
  %4 = trunc i64 %3 to i32
  ret i32 %4
}

And it calls _ZN4main4main17hf586f8163becbb21E which is:

; main::main
; Function Attrs: uwtable
define internal void @_ZN4main4main17hf586f8163becbb21E() unnamed_addr #2 {
start:
  %_0.i1 = alloca { ptr, ptr }, align 8
  %_0.i = alloca { ptr, ptr }, align 8
  %_13 = alloca i64, align 8
  %_10 = alloca i64, align 8
  %_7 = alloca [2 x { ptr, ptr }], align 8
  %_3 = alloca %"core::fmt::Arguments<'_>", align 8
; call <i64 as main::Trait>::abs
  %0 = call i64 @"_ZN35_$LT$i64$u20$as$u20$main..Trait$GT$3abs17hdeedb2e9811ae8d2E"(i64 1)
  store i64 %0, ptr %_10, align 8
  store ptr %_10, ptr %_0.i1, align 8
  %1 = getelementptr inbounds { ptr, ptr }, ptr %_0.i1, i32 0, i32 1
  store ptr @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i64$GT$3fmt17hd936d7e0310c6824E", ptr %1, align 8
  %2 = load ptr, ptr %_0.i1, align 8, !nonnull !4, !align !6, !noundef !4
  %3 = getelementptr inbounds { ptr, ptr }, ptr %_0.i1, i32 0, i32 1
  %4 = load ptr, ptr %3, align 8, !nonnull !4, !noundef !4
  %5 = insertvalue { ptr, ptr } poison, ptr %2, 0
  %6 = insertvalue { ptr, ptr } %5, ptr %4, 1
  %_8.0 = extractvalue { ptr, ptr } %6, 0
  %_8.1 = extractvalue { ptr, ptr } %6, 1
; call core::num::<impl i64>::abs
  %7 = call i64 @"_ZN4core3num21_$LT$impl$u20$i64$GT$3abs17h20f6cd07141369a2E"(i64 1)
  store i64 %7, ptr %_13, align 8
  store ptr %_13, ptr %_0.i, align 8
  %8 = getelementptr inbounds { ptr, ptr }, ptr %_0.i, i32 0, i32 1
  store ptr @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i64$GT$3fmt17hd936d7e0310c6824E", ptr %8, align 8
  %9 = load ptr, ptr %_0.i, align 8, !nonnull !4, !align !6, !noundef !4
  %10 = getelementptr inbounds { ptr, ptr }, ptr %_0.i, i32 0, i32 1
  %11 = load ptr, ptr %10, align 8, !nonnull !4, !noundef !4
  %12 = insertvalue { ptr, ptr } poison, ptr %9, 0
  %13 = insertvalue { ptr, ptr } %12, ptr %11, 1
  %_11.0 = extractvalue { ptr, ptr } %13, 0
  %_11.1 = extractvalue { ptr, ptr } %13, 1
  %14 = getelementptr inbounds [2 x { ptr, ptr }], ptr %_7, i64 0, i64 0
  %15 = getelementptr inbounds { ptr, ptr }, ptr %14, i32 0, i32 0
  store ptr %_8.0, ptr %15, align 8
  %16 = getelementptr inbounds { ptr, ptr }, ptr %14, i32 0, i32 1
  store ptr %_8.1, ptr %16, align 8
  %17 = getelementptr inbounds [2 x { ptr, ptr }], ptr %_7, i64 0, i64 1
  %18 = getelementptr inbounds { ptr, ptr }, ptr %17, i32 0, i32 0
  store ptr %_11.0, ptr %18, align 8
  %19 = getelementptr inbounds { ptr, ptr }, ptr %17, i32 0, i32 1
  store ptr %_11.1, ptr %19, align 8
; call core::fmt::Arguments::new_v1
  call void @_ZN4core3fmt9Arguments6new_v117h1b56789c8e631b99E(ptr sret(%"core::fmt::Arguments<'_>") align 8 %_3, ptr align 8 @alloc_e4a2c4720f2602b7e9c0469a96b7e954, i64 3, ptr align 8 %_7, i64 2)
; call std::io::stdio::_print
  call void @_ZN3std2io5stdio6_print17hf6a70c3631f98a8bE(ptr align 8 %_3)
  ret void
}

To be simple, it does two things:

; call <i64 as main::Trait>::abs
  %0 = call i64 @"_ZN35_$LT$i64$u20$as$u20$main..Trait$GT$3abs17hdeedb2e9811ae8d2E"(i64 1)

; call core::num::<impl i64>::abs
  %7 = call i64 @"_ZN4core3num21_$LT$impl$u20$i64$GT$3abs17h20f6cd07141369a2E"(i64 1)

Other observations:

Case 1:

fn main() {
    let x = 1;
    println!("{}",` std::any::type_name_of_val(&x));
}

This snippet outputs i32.

Case 2:

fn main() {
    let x = 1;
    println!("{}", std::any::type_name_of_val(&x));
    println!("{}", x.abs());
}

This snippet does not compile:

error[E0689]: can't call method `abs` on ambiguous numeric type `{integer}`
  --> src/main.rs:16:22
   |
16 |     println!("{}", x.abs());
   |                      ^^^
   |
help: you must specify a type for this binding, like `i32`
   |
14 |     let x: i32 = 1;
   |          +++++

Case 3:

mod internal {
    pub trait Trait {
        fn abs(self) -> Self;
    }

    impl Trait for i64 {
        fn abs(self) -> Self {
            2 * self
        }
    }
}

fn main() {
    let x = 1;
    println!("{}", x.abs());

    use internal::Trait;
    println!("{}", x.abs());
}

This snippet outputs 2 1.

This behavior really surprises me, and is there any progress on this?

bors added a commit to rust-lang-ci/rust that referenced this issue Jul 20, 2024
…r=<try>

[crater] Assemble method candidates for numerical infer vars

Gauging the fallout of not allowing traits to shadow inherent methods on numerical types.

cc rust-lang#99405

r? `@ghost`
@jieyouxu jieyouxu added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Aug 22, 2024
@jieyouxu jieyouxu added A-inference Area: Type inference C-discussion Category: Discussion or questions that doesn't represent real issues. and removed C-bug Category: This is a bug. labels Aug 22, 2024
@jieyouxu
Copy link
Member

See also discussions in #121453.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-inference Area: Type inference A-trait-system Area: Trait system A-type-system Area: Type system C-discussion Category: Discussion or questions that doesn't represent real issues. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants