-
Notifications
You must be signed in to change notification settings - Fork 7
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
Unresolved (but typed) expressions in generics #252
Comments
More generally: An expression being unresolved means it has an indeterminate constant value. This includes type values. So, the expression A type being unresolved also means it has an indeterminate value, as in it is meant to become another type. Concrete types cannot become other types, but type classes do. If I had a guess at the spec: A type is unresolved if one of the following is true:
An expression is unresolved if:
Generic invocations and calls to generic routines where any of their generic arguments are unresolved are uninstantiated. In the case of calls, this probably means: overload resolution happens, if any of the routine parameters are inferred to be an unresolved type, the call still matches but is not instantiated. Maybe this should be marked in some way because this is one of the only contexts where uninstantiated symbols are valid. |
But why do we need to turn it into a type? |
Bad wording, maybe a better way of saying it is "interpreting it as a type", i.e. encountering the expression in a type context. Interpreting a value as a type would create a static type, but |
We need to be careful that |
I guess it's not needed, I thought maybe the information of the type of the value would need to be stored. Should be fine in sigmatch which sems the arguments and matches |
The compiler may be unable to resolve an expression when it is being compiled in a generic (or template?) context because it depends on a context parameter (generic or static parameter). The unresolved expression can also propagate upward, i.e.:
We don't know what
T
is, sosizeof(T)
cannot be resolved as a constant expression, so neither cansizeof(T) == 4
, so we cannot evaluate thewhen
yet, so its branches need the untyped prepass (yet to be specified). However:sizeof(T)
has a type, so it is still valid as a runtime expression, sox
has the typeint
. Whensizeof
is replaced with a non-magic proc, we also need to prevent it from being instantiated, so an unresolved expression with a type can include calls with a non-instantiated callee. Unsure if this will cause problems.The type of the unresolved expression does not have to be concrete either, we can give them generic types in the same way that routine parameters can have generic types.
For the cases where an expression depends on an unresolved parameter, I think
Item
should track it asunresolved: bool
or maybe a more powerfulusesTypevars: set[SymId]
or something like that if needed. Then this can be ignored or propagated similar to the type. This is a divergence from the old compiler which generatestyFromExpr
here even though the expression is not a type.Edit: There is also this case:
So maybe there could also be an internal pragma specifically for consts that marks that their value is unresolved, maybe also storing the typevars it depends on.
The tricky part is when an unresolved expression is meant to become a type.
foo(T)
is an unresolved expression with a type that we know only satisfiestypedesc
. We need to givex
a type but(call)
nodes do not satisfy type AST, so we need something liketyFromExpr
in the old compiler. This would also be used insigmatch
.If we just have something like
(unresolved (call foo T))
same astyFromExpr
, then every time we encounter it in sigmatch, we would need to try to instantiate it in caseT
has been resolved. To make this more efficient, we could also store a list of the type parameters used by the expression inside the(unresolved)
type i.e.(unresolved (params T) (call foo T))
. But it's unclear if the used type parameters would be reliably tracked and it might not be that much of a problem in comparison to just try to instantiate it.We could also store the type that the original unresolved expression had before being converted into a type as the "base type" of the
unresolved
type, becoming(unresolved (typedesc) (params T) (call foo T))
. This would store concept information etc. without having to semcheck the expression again to get its type, again minimizing the work sigmatch has to do.I'm not sure if these additions are just redunancies for efficiency/simplicity or if they are needed for correctness. Maybe they are wrong too. We should probably start with them to make the implementation in
sigmatch
simpler.The final case that comes to mind is unresolved expressions as static types. The direction we've been going is that expressions that are types always have the type
typedesc
, but values as types should always be wrapped instatic
(currentlyarray
violates this by just storing an integer but it should probably userange
anyway). So it would be enough that:gives
(invoke Bar (unresolved (int) (params T) (sizeof T)))
. Then when theunresolved
type is finally instantiated, if the expression has the typetypedesc
then it is skipped, otherwise it is wrapped instatic
. In any case this would just mirror whatsemLocalTypeImpl
does for arbitrary expressions.The text was updated successfully, but these errors were encountered: