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

Does the return of a template function have template phase? #3477

Open
josh11b opened this issue Dec 8, 2023 · 4 comments
Open

Does the return of a template function have template phase? #3477

josh11b opened this issue Dec 8, 2023 · 4 comments
Labels
leads question A question for the leads team

Comments

@josh11b
Copy link
Contributor

josh11b commented Dec 8, 2023

Summary of issue:

#2153 does not address whether the return type of a template function has template phase

Details:

Interesting examples:

fn TemplateIdentity[template T:! type](x: T) -> T { return x; } 
fn Caller[T:! I](x: T) {
  // Does the type of `y` have template phase or is its type precisely `T`?
  let y: auto = TemplateIdentity(x);
}

// Do we want a generic caller to need a constraint that ensures `T.U` is defined to call this function?
fn TemplateNonIdentity[template T:! type](x: T) -> T.U { return x.F(); } 
@josh11b josh11b added the leads question A question for the leads team label Dec 8, 2023
@josh11b josh11b changed the title Is the return of a template function have template phase? Does the return of a template function have template phase? Dec 8, 2023
@chandlerc
Copy link
Contributor

My initial idea here is that the answer should be "no", but maybe even more than suggested above: maybe it should be an error to declare a function with a return type that is dependent on a template parameter?

And to make the examples work above, the declaration would have to explicitly make its return type itself a template -- I could imagine many syntaxes here, for example fn TemplateIdentity(template T:! type](x: T) -> template T { return x; }. But probably others as well.

The advantage I could see here is avoiding surprising or subtle dependence on a template, especially in functions that use a mixture of checked and template parameters and intend to have the return type only involve the checked ones.

But maybe there are disadvantages here that I'm not seeing that make it important to do this automatically?


Either way, once the return type is a template (implicitly or explicitly), I would propagate that to the type of the call expression but not to something like auto.

To try to illustrate with examples:

// However we want to write a function that *definitely* has a template return type.
fn TemplateIdentity[template T:! type](x: T) -> template T { return x; }

// Possible type...
class C {
  var x: i32;
}

fn GenericCaller[T:! type](v: T) {
  // Because we can call templates from a generic, this is allowed in the definition
  // but will require instantiation that may fail.
  let y1: i32 = TemplateIdentity(v).x;

  // This is allowed...
  let tmp: auto = TemplateIdentity(v);

  // But this is not...
  let y2: i32 = tmp.x;

  // Both of these are fine...
  let template ttmp: auto = TemplateIdentity(v);
  let y3: i32 = ttmp.x;
}

var c: C;
GenericCaller(c);

@josh11b
Copy link
Contributor Author

josh11b commented Jan 3, 2024

An alternative to consider: the type of the return value has the same phase as the return type expression. So if it is dependent on a template parameter, it has template phase. Motivation for this approach includes (a) it is a simple rule consistent with our rules for template phase elsewhere in the language, (b) it allows constructs that require template phase, and (c) if the caller doesn't want a template phase value, the caller can then convert to symbolic phase using a let binding. Note though that the "just returning T" case might be expected to match the caller's phase by users, so this might be surprising behavior.

@chandlerc
Copy link
Contributor

An alternative to consider: the type of the return value has the same phase as the return type expression. So if it is dependent on a template parameter, it has template phase. Motivation for this approach includes (a) it is a simple rule consistent with our rules for template phase elsewhere in the language, (b) it allows constructs that require template phase, and (c) if the caller doesn't want a template phase value, the caller can then convert to symbolic phase using a let binding. Note though that the "just returning T" case might be expected to match the caller's phase by users, so this might be surprising behavior.

It makes sense, but I think I still lean towards requiring the signature of the function to explicitly opt into it's return type having template phase. Inferring that from how the return is written in the body seems relatively brittle to me. In my comment, I gave the example of an author that didn't intend for a template phase expression to end up in the return and it doing so accidentally -- it seems like that would be an issue here.

@josh11b
Copy link
Contributor Author

josh11b commented Jan 9, 2024

It makes sense, but I think I still lean towards requiring the signature of the function to explicitly opt into it's return type having template phase. Inferring that from how the return is written in the body seems relatively brittle to me. In my comment, I gave the example of an author that didn't intend for a template phase expression to end up in the return and it doing so accidentally -- it seems like that would be an issue here.

I meant that it would be determined by the return type expression (after the -> in the signature), not the return expression (after return in the body of the function).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
leads question A question for the leads team
Projects
None yet
Development

No branches or pull requests

2 participants