v icon indicating copy to clipboard operation
v copied to clipboard

Problems with string interpolation of `SomeStruct<T>` as well as methods on generic structs, returning `SomeStruct<&T>`

Open spytheman opened this issue 2 years ago • 8 comments

V doctor:

OS: linux, Ubuntu 20.04.3 LTS
Processor: 4 cpus, 64bit, little endian, Intel(R) Core(TM) i3-3225 CPU @ 3.30GHz
CC version: cc (Ubuntu 10.3.0-1ubuntu1~20.04) 10.3.0

getwd: /v/vnew
vmodules: /home/delian/.vmodules
vroot: /v/vnew
vexe: /v/vnew/v
vexe mtime: 2022-11-05 12:04:10
is vroot writable: true
is vmodules writable: true
V full version: V 0.3.2 e81e0ac.27d8f23

Git version: git version 2.25.1
Git vroot status: 0.3.2-60-g27d8f237
.git/config present: true
thirdparty/tcc status: thirdparty-linux-amd64 12f392c3

What did you do? v -g -o vdbg cmd/v && vdbg bCQbVgRp.v

module main

struct None {}
pub type Maybe<T> = None | T

pub fn (m Maybe<T>) str<T>() string {
	return if m is T {
		x := m as T
		'Some($x)'
	} else {
		'Noth'
	}
}

pub fn some<T>(v T) Maybe<T> {
	return Maybe<T>(v)
}

pub fn noth<T>() Maybe<T> {
	return Maybe<T>(None{})
}

pub fn (m Maybe<T>) is_some<T>() bool {
	return match m {
		None { false }
		T { true }
	}
}

pub fn (m Maybe<T>) is_noth<T>() bool {
	return match m {
		None { true }
		T { false }
	}
}

pub fn (m Maybe<T>) unwrap<T>() T {
	if m is T {
		return m
	}
	panic("can't unwrap Noth value")
}

pub fn (m Maybe<T>) unwrap_or<T>(noth_value T) T {
	return match m {
		None { noth_value }
		T { m }
	}
}

/// Boolean operators
pub fn (m Maybe<T>) and<T>(m2 Maybe<T>) Maybe<T> {
	return match m {
		None { noth<T>() }
		T { m2 }
 	}
}

pub fn (m Maybe<T>) and_then<T>(f fn (v T) Maybe<T>) Maybe<T> {
	return match m {
		None { noth<T>() }
		T { f(m) }
	}
}

pub fn (m Maybe<T>) @or<T>(m2 Maybe<T>) Maybe<T> {
 	return match m {
		None {
			match m2 {
				None { None{} }
				T { m2 }
			}
		}
		T {
			m
		}
	}
}

pub fn (m Maybe<T>) or_else<T>(f fn () Maybe<T>) Maybe<T> {
	return match m {
		None { f() }
		T { m }
	}
}

pub fn (m Maybe<T>) xor<T>(m2 Maybe<T>) Maybe<T> {
	if m is None && m2 is None {
		return noth<T>()
	}
	if m2 is None {
		return m
	}
	return m2
}

/// Modifier operations
//pub fn (m Maybe<T>) map<T,U>(f fn (v T) U) Maybe<U> {
//	return match m {
//		None { noth<U>() }
// 		T {
//	 		some<U>(f(m))
//		}
// 	}
//}


pub fn (m Maybe<T>) filter<T>(predicate fn (v T) bool) Maybe<T> {
	match m {
		None { return None{} }
		T {
			if !predicate(m) {
				return None{}
			}
			return some<T>(m)
		}
	}
}

/// Utils methods
// pub fn (m Maybe<T>) flatten<T>() Maybe<T> {
//	// TODO:
//}

pub fn (m &Maybe<T>) as_ref<T>() Maybe<&T> {
	return match m {
		None { noth<&T>() }
		T { 
			mut ref := voidptr(unsafe { &m })
			some<&T>(ref)
		}
	}
}

fn main() {
	a := some(123)
	b := some('abc')
	println(a.str()) // works
	println(b.str()) // works
	println(a.unwrap()) // works
	ra := a.as_ref() // compiles, but is buggy, since the inferred type is MayBe<int>, and NOT the correct MayBe<&int> .
	explicit_ra := a.as_ref<&int>() // also compiles, but is buggy, since the inferred return type is `MayBe<int>`, and NOT the correct MayBe<&int> .
	
	println(ra.str()) // works
	println(ra.unwrap().str()) // works
	println(explicit_ra.str()) // works
	//
	// c := a.@or(b) // cgen error
	// println(a) // cgen error
	// println(b) // cgen error
	// println(ra) // cgen error
	// println('$a') // cgen error
}

What did you expect to see?

A program that compiles even when the commented lines at the end are uncommented.

What did you see instead?

Some(123)
Some(abc)
123
Some(-134639648)
-134639648
Some(-134639648)

spytheman avatar Nov 05 '22 17:11 spytheman

c := a.@or(b) should be a checker error, since the type of a is Maybe<int>, while the type of b is Maybe<string> i.e. incompatible.

spytheman avatar Nov 05 '22 17:11 spytheman

println(a) should work, just like println(a.str()) does

spytheman avatar Nov 05 '22 17:11 spytheman

println('$a') should work too, just like println(a.str()) does

spytheman avatar Nov 05 '22 17:11 spytheman

ra := a.as_ref() not inferring that the return type should be Maybe<&int> and NOT Maybe<int> as it does, is the weirdest of all listed bugs here, and probably the most important as well, since imho it can not be worked around easily.

spytheman avatar Nov 05 '22 17:11 spytheman

Uncommenting the pub fn (m Maybe<T>) map<T,U>(f fn (v T) U) Maybe<U> { method also leads to weird checker errors, that are not directly related to the map method: image

spytheman avatar Nov 05 '22 17:11 spytheman

In the original report, there was also this:

// pub fn (m Maybe<Maybe<T>>) flatten() Maybe<T> {
//  return m.unwrap_or(noth<T>())
// }

... which I could not express with V, while it does serve a useful functionality 🤔 . Not sure what could be done about it.

spytheman avatar Nov 05 '22 17:11 spytheman

The only remaining issue is: ra := a.as_ref() is not inferring, that the return type should be Maybe<&int> and NOT Maybe as it does: dump( typeof(ra).name ) produces: [g.v:142] typeof(ra).name: Maybe<int>, but it should be: [g.v:142] typeof(ra).name: Maybe<&int>

spytheman avatar Nov 13 '22 10:11 spytheman

Current output:

PS D:\Games\Proekti\V\interactions> type test.v 
module main

struct None {}

pub type Maybe[T] = None | T

pub fn (m Maybe[T]) str[T]() string {
        return if m is T {
                x := m as T
                'Some(${x})'
        } else {
                'Noth'
        }
}

pub fn some[T](v T) Maybe[T] {
        return Maybe[T](v)
}

pub fn noth[T]() Maybe[T] {
        return Maybe[T](None{})
}

pub fn (m Maybe[T]) is_some[T]() bool {
        return match m {
                None { false }
                T { true }
        }
}

pub fn (m Maybe[T]) is_noth[T]() bool {
        return match m {
                None { true }
                T { false }
        }
}

pub fn (m Maybe[T]) unwrap[T]() T {
        if m is T {
                return m
        }
        panic("can't unwrap Noth value")
}

pub fn (m Maybe[T]) unwrap_or[T](noth_value T) T {
        return match m {
                None { noth_value }
                T { m }
        }
}

/// Boolean operators
pub fn (m Maybe[T]) and[T](m2 Maybe[T]) Maybe[T] {
        return match m {
                None { noth[T]() }
                T { m2 }
        }
}

pub fn (m Maybe[T]) and_then[T](f fn (v T) Maybe[T]) Maybe[T] {
        return match m {
                None { noth[T]() }
                T { f(m) }
        }
}

pub fn (m Maybe[T]) @or[T](m2 Maybe[T]) Maybe[T] {
        return match m {
                None {
                        match m2 {
                                None { None{} }
                                T { m2 }
                        }
                }
                T {
                        m
                }
        }
}

pub fn (m Maybe[T]) or_else[T](f fn () Maybe[T]) Maybe[T] {
        return match m {
                None { f() }
                T { m }
        }
}

pub fn (m Maybe[T]) xor[T](m2 Maybe[T]) Maybe[T] {
        if m is None && m2 is None {
                return noth[T]()
        }
        if m2 is None {
                return m
        }
        return m2
}

/// Modifier operations
// pub fn (m Maybe<T>) map<T,U>(f fn (v T) U) Maybe<U> {
//      return match m {
//              None { noth<U>() }
//              T {
//                      some<U>(f(m))
//              }
//      }
//}

pub fn (m Maybe[T]) filter[T](predicate fn (v T) bool) Maybe[T] {
        match m {
                None {
                        return None{}
                }
                T {
                        if !predicate(m) {
                                return None{}
                        }
                        return some[T](m)
                }
        }
}

/// Utils methods
// pub fn (m Maybe<T>) flatten<T>() Maybe<T> {
//      // TODO:
//}
pub fn (m &Maybe[T]) as_ref[T]() Maybe[&T] {
        return match m {
                None {
                        noth[&T]()
                }
                T {
                        mut ref := voidptr(unsafe { &m })
                        some[&T](ref)
                }
        }
}

fn main() {
        a := some(123)
        b := some('abc')
        println(a.str()) // works
        println(b.str()) // works
        println(a.unwrap()) // works
        ra := a.as_ref() // compiles, but is buggy, since the inferred type is MayBe<int>, and NOT the correct MayBe<&int> .
        explicit_ra := a.as_ref[&int]() // also compiles, but is buggy, since the inferred return type is `MayBe<int>`, and NOT the correct MayBe<&int> .
        println(ra.str()) // works
        println(ra.unwrap().str()) // works
        println(explicit_ra.str()) // works
        //
        // c := a.@or(b) // cgen error
        // println(a) // cgen error
        // println(b) // cgen error
        // println(ra) // cgen error
        // println('$a') // cgen error
}
PS D:\Games\Proekti\V\interactions> v version
V 0.4.4 febc4a7
PS D:\Games\Proekti\V\interactions> v run test.v
test.v:17:9: error: unknown type `Maybe[&int]`.
Did you mean `Maybe[int]`?
   15 |
   16 | pub fn some[T](v T) Maybe[T] {
   17 |     return Maybe[T](v)
      |            ~~~~~~~~~~~
   18 | }
   19 |
test.v:17:9: error: unknown type `Maybe[&string]`.
Did you mean `Maybe[string]`?
   15 |
   16 | pub fn some[T](v T) Maybe[T] {
   17 |     return Maybe[T](v)
      |            ~~~~~~~~~~~
   18 | }
   19 |
test.v:21:9: error: unknown type `Maybe[&int]`.
Did you mean `Maybe[int]`?
   19 |
   20 | pub fn noth[T]() Maybe[T] {
   21 |     return Maybe[T](None{})
      |            ~~~~~~~~~~~~~~~~
   22 | }
   23 |
test.v:21:9: error: cannot cast struct `None` to `Maybe[&int]`
   19 |
   20 | pub fn noth[T]() Maybe[T] {
   21 |     return Maybe[T](None{})
      |            ~~~~~~~~~~~~~~~~
   22 | }
   23 |
test.v:21:9: error: unknown type `Maybe[&string]`.
Did you mean `Maybe[string]`?
   19 |
   20 | pub fn noth[T]() Maybe[T] {
   21 |     return Maybe[T](None{})
      |            ~~~~~~~~~~~~~~~~
   22 | }
   23 |
test.v:21:9: error: cannot cast struct `None` to `Maybe[&string]`
   19 |
   20 | pub fn noth[T]() Maybe[T] {
   21 |     return Maybe[T](None{})
      |            ~~~~~~~~~~~~~~~~
   22 | }
   23 |
If the code of your project is in multiple files, try with `v .` instead of `v test.v`
If the code of your project is in multiple files, try with `v .` instead of `v test.v`
If the code of your project is in multiple files, try with `v .` instead of `v test.v`
If the code of your project is in multiple files, try with `v .` instead of `v test.v`

MCausc78 avatar Mar 02 '24 00:03 MCausc78