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

undefined behavior when passing a generic with auto-return to a proc #16906

Open
timotheecour opened this issue Feb 1, 2021 · 2 comments
Open

Comments

@timotheecour
Copy link
Member

timotheecour commented Feb 1, 2021

Example 1

(EDIT: consequence of Example 3)
sugar.=> doesn't work well with void return type

when true:
  import sugar
  proc bar4(f: int -> int) = discard
  bar4(x => x) # ok

  proc bar5(f: int -> void) = discard
  bar5(x => (discard)) # bug: error
  # bar5(x => echo x) # ditto
  # bar5((x: int) => (discard)) # workaround: works

Current Output

t11794.nim(11, 7) Error: type mismatch: got <proc (x: GenericParam): untyped>
but expected one of:
proc bar5(f: int -> void) [proc declared in t11794.nim(10, 8)]
  first type mismatch at position: 1
  required type for f: proc (i0: int){.closure.} [proc]
  but expression 'proc (x: auto): auto =
  discard' is of type: proc (x: GenericParam): untyped [proc]

expression: bar5(proc (x: auto): auto =
  discard )
    bar5(x => (discard)) # bug: error

Expected Output

works

Example 2

in above example, the error mentions proc (x: GenericParam): untyped, which is suspicious (proc can't have untyped return type).

Example 3: undefined behavior

(added this after further investigation)
this example is more troubling, and shows what's probably the root cause for all examples here:
when passing a generic with auto-return type to a proc, the generic return type is implicitly cast to the return type of the param instead of instantiating the generic and type-checking the return type against the param return type.

This results in undefined behavior:

when true:
  proc bar(f: proc(a: string): (float, float)): (float, float) = f("abc")
  proc fun(x: string or int8): auto = x
  # proc fun[T](x: T): auto = x # ditto
  # proc fun(x: auto): auto = x # ditto
  # proc fun(x: string): auto = x # would correctly give CT error
  echo bar(fun) # prints stack garbage (0.0, 3.237859210020609e-319)
  echo bar(fun) # prints (0.0, 0.0)

Additional Information

1.5.1 91ace21
/cc @hlaaftana

@metagn
Copy link
Collaborator

metagn commented Feb 2, 2021

=> has auto as a return type by default, when you put x its type by default is also auto, so you get this proc:

proc bar(x: auto): auto = discard

and the example becomes:

proc foo(p: proc (x: int)) = discard
proc bar(x: auto): auto = discard
foo(bar)

which gives the same error. This too (so not specific to auto params):

proc foo(p: proc (x: int)) = discard
proc bar[T](x: T): auto = discard
foo(bar)

If you change the return type from auto to void then it compiles.

@timotheecour timotheecour changed the title sugar.=> doesn't work well with void return type undefined behavior when passing a generic with auto-return to a proc Feb 2, 2021
@timotheecour
Copy link
Member Author

timotheecour commented Feb 2, 2021

after investigating further, it turns out the root cause is that generics with auto-return aren't being typed-checked properly when passed to a proc, and their type is implicitly cast to the return type of the generic param (when non-void), leading to all sorts of issues like Example 1, or undefined behavior as in Example 3 which I added in issue message.

Araq pushed a commit that referenced this issue Jan 17, 2024
fixes #23200, fixes #18866

#21065 made it so `auto` proc return types remained as `tyAnything` and
not turned to `tyUntyped`. This had the side effect that anything
previously bound to `tyAnything` in the proc type match was then bound
to the proc return type, which is wrong since we don't know the proc
return type even if we know the expected parameter types (`tyUntyped`
also [does not care about its previous bindings in
`typeRel`](https://github.com/nim-lang/Nim/blob/ab4278d2179639f19967431a7aa1be858046f7a7/compiler/sigmatch.nim#L1059-L1061)
maybe for this reason).

Now we mark `tyAnything` return types for routines as `tfRetType` [as
done for other meta return
types](https://github.com/nim-lang/Nim/blob/18b5fb256d4647efa6a64df451d37129d36e96f3/compiler/semtypes.nim#L1451),
and ignore bindings to `tyAnything` + `tfRetType` types in `semtypinst`.
On top of this, we reset the type relation in `paramTypesMatch` only
after creating the instantiation (instead of trusting
`isInferred`/`isInferredConvertible` before creating the instantiation),
using the same mechanism that `isBothMetaConvertible` uses.

This fixes the issues as well as making the disabled t15386_2 test
introduced in #21065 work. As seen in the changes for the other tests,
the error messages give an obscure `proc (a: GenericParam): auto` now,
but it does give the correct error that the overload doesn't match
instead of matching the overload pre-emptively and expecting a specific
return type.

tsugar had to be changed due to #16906, which is the problem where
`void` is not inferred in the case where `result` was never touched.
narimiran pushed a commit that referenced this issue Apr 19, 2024
fixes #23200, fixes #18866

#21065 made it so `auto` proc return types remained as `tyAnything` and
not turned to `tyUntyped`. This had the side effect that anything
previously bound to `tyAnything` in the proc type match was then bound
to the proc return type, which is wrong since we don't know the proc
return type even if we know the expected parameter types (`tyUntyped`
also [does not care about its previous bindings in
`typeRel`](https://github.com/nim-lang/Nim/blob/ab4278d2179639f19967431a7aa1be858046f7a7/compiler/sigmatch.nim#L1059-L1061)
maybe for this reason).

Now we mark `tyAnything` return types for routines as `tfRetType` [as
done for other meta return
types](https://github.com/nim-lang/Nim/blob/18b5fb256d4647efa6a64df451d37129d36e96f3/compiler/semtypes.nim#L1451),
and ignore bindings to `tyAnything` + `tfRetType` types in `semtypinst`.
On top of this, we reset the type relation in `paramTypesMatch` only
after creating the instantiation (instead of trusting
`isInferred`/`isInferredConvertible` before creating the instantiation),
using the same mechanism that `isBothMetaConvertible` uses.

This fixes the issues as well as making the disabled t15386_2 test
introduced in #21065 work. As seen in the changes for the other tests,
the error messages give an obscure `proc (a: GenericParam): auto` now,
but it does give the correct error that the overload doesn't match
instead of matching the overload pre-emptively and expecting a specific
return type.

tsugar had to be changed due to #16906, which is the problem where
`void` is not inferred in the case where `result` was never touched.

(cherry picked from commit f46f26e)
narimiran pushed a commit that referenced this issue Apr 27, 2024
fixes #23200, fixes #18866

not turned to `tyUntyped`. This had the side effect that anything
previously bound to `tyAnything` in the proc type match was then bound
to the proc return type, which is wrong since we don't know the proc
return type even if we know the expected parameter types (`tyUntyped`
also [does not care about its previous bindings in
`typeRel`](https://github.com/nim-lang/Nim/blob/ab4278d2179639f19967431a7aa1be858046f7a7/compiler/sigmatch.nim#L1059-L1061)
maybe for this reason).

Now we mark `tyAnything` return types for routines as `tfRetType` [as
done for other meta return
types](https://github.com/nim-lang/Nim/blob/18b5fb256d4647efa6a64df451d37129d36e96f3/compiler/semtypes.nim#L1451),
and ignore bindings to `tyAnything` + `tfRetType` types in `semtypinst`.
On top of this, we reset the type relation in `paramTypesMatch` only
after creating the instantiation (instead of trusting
`isInferred`/`isInferredConvertible` before creating the instantiation),
using the same mechanism that `isBothMetaConvertible` uses.

This fixes the issues as well as making the disabled t15386_2 test
introduced in #21065 work. As seen in the changes for the other tests,
the error messages give an obscure `proc (a: GenericParam): auto` now,
but it does give the correct error that the overload doesn't match
instead of matching the overload pre-emptively and expecting a specific
return type.

tsugar had to be changed due to #16906, which is the problem where
`void` is not inferred in the case where `result` was never touched.

(cherry picked from commit f46f26e)
narimiran pushed a commit that referenced this issue Apr 27, 2024
fixes #23200, fixes #18866

not turned to `tyUntyped`. This had the side effect that anything
previously bound to `tyAnything` in the proc type match was then bound
to the proc return type, which is wrong since we don't know the proc
return type even if we know the expected parameter types (`tyUntyped`
also [does not care about its previous bindings in
`typeRel`](https://github.com/nim-lang/Nim/blob/ab4278d2179639f19967431a7aa1be858046f7a7/compiler/sigmatch.nim#L1059-L1061)
maybe for this reason).

Now we mark `tyAnything` return types for routines as `tfRetType` [as
done for other meta return
types](https://github.com/nim-lang/Nim/blob/18b5fb256d4647efa6a64df451d37129d36e96f3/compiler/semtypes.nim#L1451),
and ignore bindings to `tyAnything` + `tfRetType` types in `semtypinst`.
On top of this, we reset the type relation in `paramTypesMatch` only
after creating the instantiation (instead of trusting
`isInferred`/`isInferredConvertible` before creating the instantiation),
using the same mechanism that `isBothMetaConvertible` uses.

This fixes the issues as well as making the disabled t15386_2 test
introduced in #21065 work. As seen in the changes for the other tests,
the error messages give an obscure `proc (a: GenericParam): auto` now,
but it does give the correct error that the overload doesn't match
instead of matching the overload pre-emptively and expecting a specific
return type.

tsugar had to be changed due to #16906, which is the problem where
`void` is not inferred in the case where `result` was never touched.

(cherry picked from commit f46f26e)
narimiran pushed a commit that referenced this issue Apr 27, 2024
fixes #23200, fixes #18866

not turned to `tyUntyped`. This had the side effect that anything
previously bound to `tyAnything` in the proc type match was then bound
to the proc return type, which is wrong since we don't know the proc
return type even if we know the expected parameter types (`tyUntyped`
also [does not care about its previous bindings in
`typeRel`](https://github.com/nim-lang/Nim/blob/ab4278d2179639f19967431a7aa1be858046f7a7/compiler/sigmatch.nim#L1059-L1061)
maybe for this reason).

Now we mark `tyAnything` return types for routines as `tfRetType` [as
done for other meta return
types](https://github.com/nim-lang/Nim/blob/18b5fb256d4647efa6a64df451d37129d36e96f3/compiler/semtypes.nim#L1451),
and ignore bindings to `tyAnything` + `tfRetType` types in `semtypinst`.
On top of this, we reset the type relation in `paramTypesMatch` only
after creating the instantiation (instead of trusting
`isInferred`/`isInferredConvertible` before creating the instantiation),
using the same mechanism that `isBothMetaConvertible` uses.

This fixes the issues as well as making the disabled t15386_2 test
introduced in #21065 work. As seen in the changes for the other tests,
the error messages give an obscure `proc (a: GenericParam): auto` now,
but it does give the correct error that the overload doesn't match
instead of matching the overload pre-emptively and expecting a specific
return type.

tsugar had to be changed due to #16906, which is the problem where
`void` is not inferred in the case where `result` was never touched.

(cherry picked from commit f46f26e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants