Skip to content

Permit -> _ return types for improved diagnostics #56132

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

Closed
varkor opened this issue Nov 21, 2018 · 31 comments · Fixed by #62694
Closed

Permit -> _ return types for improved diagnostics #56132

varkor opened this issue Nov 21, 2018 · 31 comments · Fixed by #62694
Labels
A-diagnostics Area: Messages for errors, warnings, and lints disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. finished-final-comment-period The final comment period is finished for this PR / Issue. P-high High priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@varkor
Copy link
Member

varkor commented Nov 21, 2018

Consider (the invalid Rust code containing) a function that returns a value, but has no return type:

fn foo() {
    0u8
}

fn main() {
    let _: u8 = foo();
}

Currently, the compiler produces these errors:

error[E0308]: mismatched types
 --> src/main.rs:2:5
  |
1 | fn foo() {
  |          - help: try adding a return type: `-> u8`
2 |     0u8
  |     ^^^ expected (), found u8
  |
  = note: expected type `()`
             found type `u8`

error[E0308]: mismatched types
 --> src/main.rs:6:17
  |
6 |     let _: u8 = foo();
  |                 ^^^^^ expected u8, found ()
  |
  = note: expected type `u8`
             found type `()`

error: aborting due to 2 previous errors

In theory, the only problem is the missing return type, which, if provided, would cause the code to compile successfully.

The compiler knows what the return type should be here, and could potentially interpret the rest of the program as if the correct return type were given.

This issue proposes giving special treatment in the compiler for diagnostics only (note that this is not a language change) to the syntax -> _ for return types. This would report a normal error for a missing return type (no extra code would compile after this change), but would otherwise treat the function as having the missing return type. A machine-applicable suggestion would be provided to replace _ with the correct return type.

For example:

fn foo() -> _ {
    0u8
}

fn main() {
    let _: u8 = foo();
}
error[E0121]: the type placeholder `_` is not allowed within types on item signatures
 --> src/main.rs:1:13
  |
1 | fn foo() -> _ {
  |             ^ not allowed in type signatures
  |             - help: replace `_` with the correct return type: `u8`
@varkor varkor added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 21, 2018
@varkor
Copy link
Member Author

varkor commented Nov 21, 2018

This change would potentially set precedent for treating specific invalid/incomplete code specially in ways that are not reflected in valid code. In other words:

eddyb: we need to write up something about "guaranteed-error/diagnostic-only" "features", where the compiler does things it shouldn't do in strict terms of the Rust language, knowing it will error anyway, so any power the user gets is only in diagnostics, not in compiled code

This was originally proposed by @eddyb on Discord.

Relevant conversation:

eddyb: if you can convince people to make -> _ a compiler error that contains the type to put in there and provides it to callers (NB dependency cycles disallowed), I'll do it
varkor: I would have thought that -> _ could be given a type-inference based diagnostic approach right now, without an RFC or anything
varkor: it seems like a reasonable diagnostic improvement
eddyb: well, I want someone to confirm that :P

@varkor
Copy link
Member Author

varkor commented Nov 21, 2018

As this potentially sets precedent for diagnostic-motivated behaviour changes, it seems prudent to check that there aren't any concerns with doing this.

@rfcbot merge

@rfcbot
Copy link
Collaborator

rfcbot commented Nov 21, 2018

Team member @varkor has proposed to merge this. The next step is review by the rest of the tagged team members:

Concerns:

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Nov 21, 2018
@eddyb
Copy link
Member

eddyb commented Nov 21, 2018

Note that the implementation difficulty of this should be negligible, becuase we alrrady have on-demand queries, and we can switch between "check body against signature" and "get signature from body" trivially.

@eddyb eddyb added E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. labels Nov 21, 2018
@scottmcm
Copy link
Member

scottmcm commented Nov 21, 2018

@rfcbot reviewed Oops, not my team 😆

I support this precedent, and think it obviates things like rust-lang/rfcs#2479

@petrochenkov
Copy link
Contributor

petrochenkov commented Nov 21, 2018

Why is -> _ necessary in fn foo() -> _ { 0u8 }?
We can give exactly the same machine-applicable suggestion for fn foo() { 0u8 } without requiring typing extra characters.

@oli-obk
Copy link
Contributor

oli-obk commented Nov 21, 2018

The difference is that calls to the function would also know about the return type and not produce additional fallout. Basically return type inference like impl Trait, but transparent and only if an error was reported.

@estebank
Copy link
Contributor

estebank commented Nov 21, 2018

@rfcbot concern unneeded

An alternative would be to carry optional inferred type information on fn (and potentially all other) blocks. This way, if we present the above suggestion to change the return type to u8, we could, for the presented above code display something along the lines of:

error[E0308]: mismatched types
 --> src/main.rs:2:5
  |
1 | fn foo() {
  |          - help: try adding a return type: `-> u8`
2 |     0u8
  |     ^^^ expected return type (), found u8
...
6 |     let _: u8 = foo();
  |                 ----- expected u8 here as well
  |
  = note: expected type `()`
             found type `u8`

or just completely elide the error in line 6 by checking against both the resolved type (() here) and the suggested type (u8) if either match, do not emit that diagnostic. We already do something along these lines on blocks that have either parse or type errors, by marking them as TyErr and not emitting mismatched types errors that involve TyErr (as they have already being emitted in the source of the problem).

@leonardo-m
Copy link

leonardo-m commented Nov 21, 2018

I think in the Haskell world for something like this they invent a pretty name (something like Return Type Holes) and write a paper on it.

@eddyb
Copy link
Member

eddyb commented Nov 21, 2018

@estebank That's not actionable because it breaks with cycles (think recursive function) - and we don't want to add recovery from dependency cycles.

The way it works with -> _ is that it's a switch for flipping a dependency edge in the compiler.

@estebank
Copy link
Contributor

Couldn't this (changing -> () to -> _) be done implicitly by performing that change once the suggestion is emitted?

@eddyb
Copy link
Member

eddyb commented Nov 21, 2018

@estebank No, that sort of side-effect is incompatible with the incremental dataflow ("query") model.

@Centril
Copy link
Contributor

Centril commented Nov 22, 2018

@scottmcm

I support this precedent, and think it obviates things like rust-lang/rfcs#2479

Obviously I disagree that it obviates that change; this will still error, not letting you finish cargo check nor cargo test so the experience is not the same.

That said I fully support this change.

@FrankSD
Copy link

FrankSD commented Dec 9, 2018

Is this issue already taken? Please confirm I would like to take this on

@varkor
Copy link
Member Author

varkor commented Dec 19, 2018

@FrankSD: it's not, but you'll want to wait until the FCP has (started and) finished before working on this, to avoid wasting time if the FCP does not pass.

@estebank
Copy link
Contributor

estebank commented Feb 5, 2019

I have philosophical disagreements with this idea, as I'd prefer the compiler to not need this (as I see it) kludge and instead provide better diagnostics with more advanced inference, but I understand the pragmatic reasoning behind it. +1

@eddyb
Copy link
Member

eddyb commented Feb 13, 2019

It's hard to produce disagnostics if you can't parse the source.
Also, typed holes are not a kludge, check out http://hazel.org.

@joshtriplett
Copy link
Member

@FrankSD
Copy link

FrankSD commented Apr 1, 2019

Anyone still working on this?

@varkor
Copy link
Member Author

varkor commented Apr 1, 2019

@estebank: you need to resolve your previous concern, for FCP to begin.

@estebank
Copy link
Contributor

estebank commented Apr 5, 2019

@rfcbot resolve unneeded

@rfcbot rfcbot added the final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. label Apr 5, 2019
@rfcbot
Copy link
Collaborator

rfcbot commented Apr 5, 2019

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Apr 5, 2019
@estebank
Copy link
Contributor

estebank commented Apr 8, 2019

I've been thinking about this and in the implementation we probably should support underscore with these suggestions anywhere we accept types, not just return type annotations, like function arguments and let bindings.

@vi
Copy link
Contributor

vi commented Apr 10, 2019

Will it automatically recommend using impl Trait?

fn qqq() -> _ {
    |x:i32|{x+1}
}

Will it also work in generators / async fns?

@rfcbot
Copy link
Collaborator

rfcbot commented Apr 15, 2019

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC will be merged soon.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Apr 15, 2019
@eddyb
Copy link
Member

eddyb commented May 1, 2019

@estebank For function arguments you'd need global inference to actually know the type, other than that, the only thing you could reasonably do today is turn the _ into a hole of some sort which doesn't unify with anything (but really, that's no better than writing () or !).

As for let bindings, those are always inside bodies, so _ just works (it means "infer this").

@vi Only if we special-case it for closures.

@varkor
Copy link
Member Author

varkor commented May 6, 2019

@eddyb: are you able to provide mentoring instructions for @FrankSD?

@eddyb
Copy link
Member

eddyb commented May 6, 2019

I sadly don't have a lot of time to spend on this, but I can give some quick pointers here, and can answer questions on IRC/Discord later, if that helps.

The goal is to use something like this (which is the type -> impl Trait was inferred to be):

// existential types desugared from impl Trait
ItemKind::Existential(hir::ExistTy {
impl_trait_fn: Some(owner),
..
}) => {
tcx.typeck_tables_of(owner)
.concrete_existential_types
.get(&def_id)
.map(|opaque| opaque.concrete_type)
.unwrap_or_else(|| {

When computing a function signature:

TraitItem(hir::TraitItem {
node: TraitItemKind::Method(sig, _),
..
})
| ImplItem(hir::ImplItem {
node: ImplItemKind::Method(sig, _),
..
}) => AstConv::ty_of_fn(&icx, sig.header.unsafety, sig.header.abi, &sig.decl),
Item(hir::Item {
node: ItemKind::Fn(decl, header, _, _),
..
}) => AstConv::ty_of_fn(&icx, header.unsafety, header.abi, decl),

But only if the return type in the signature is hir::TyKind::Infer, and the function has a body (so not method declarations in traits without a default - or maybe not trait methods at all).

To avoid a cyclical dependency, this should also check for hir::TyKind::Infer return type, and use AstConv::ty_of_fn itself instead of calling tcx.fn_sig(def_id):

let fcx = if let Some(decl) = fn_decl {
let fn_sig = tcx.fn_sig(def_id);

As for where to get that inferred return type in type_of: you'll find it in the node_types map on TypeckTables, keyed by the return type (the hir::TyKind::Infer one).

@eddyb
Copy link
Member

eddyb commented May 6, 2019

Oh and you'll have to emit an error at one of those points (I'm thinking fn_sig), with the inferred type, to present it to the user and at the same time guarantee compilation doesn't finish.
(For good measure, I'd also add a delay_span_bug to check/mod.rs)

@vi
Copy link
Contributor

vi commented May 6, 2019

guarantee compilation doesn't finish

Maybe it would be nice to have some #![feature] flag to replace the error with a warning? It may be useful for prototyping, when the code is still in #![allow(unused)] era.

cargo fix should automatically type in the inferred type for me based on such warning.

@eddyb
Copy link
Member

eddyb commented May 14, 2019

Downgrading this to a warning would require a RFC, AFAIK.
As for cargo fix, I would expect it to be able to fix errors, too.
(Although the compiler can't exactly give you a nice type to replace the _ with)

@estebank estebank added the P-high High priority label May 25, 2019
lundibundi added a commit to lundibundi/rust that referenced this issue Jul 17, 2019
bors added a commit that referenced this issue Jul 19, 2019
rustc_typeck: improve diagnostics for -> _ fn return type

This should implement IIUC the mentioned issue.

~~I'm not sure if there is a better way than `get_infer_ret_ty` to get/check the return type without code duplication.~~

~~Also, is this unwrap be okay `ty::Binder::bind(*tables.liberated_fn_sigs().get(hir_id).unwrap())`?~~

r? @eddyb
Closes: #56132
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. finished-final-comment-period The final comment period is finished for this PR / Issue. P-high High priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.