Closed ta0kira closed 3 years ago
It could also be a bit of work to include this in the formalization of the type system.
This looks like a problem, but it might not be:
@value interface Foo {
bar () -> (#self)
}
concrete Something {
@value call<#x>
#x allows Foo // <-- what is #self in Foo.bar?
() -> (#x)
}
This is probably a non-issue because you can't call Foo.bar
on #x
without #x requires Foo
.
Separately, with something like #x defines Foo
where Foo.bar
uses #self
, there will need to be a step where #self
is replaced with #x
when a call to #x.bar
is resolved within a procedure. This can likely be done in ccGetTypeFunction
.
Actually, I don't know if param filters are special here; I think we just need to replace #self
with whatever the call is being bound to, i.e., the type of the value for @value
functions or the type instance for @type
functions.
Static replacements needed:
concrete
categories.It seems like there should be an issue using #self
in @value interface
since such interfaces can't be used as @value
types; however, it isn't an issue, because such functions will always be called from something that can be a @value
type.
Static replacement is also needed in reduce
and typename
calls.
Something I inadvertently accounted for in the solution is when #self
is an intersection type:
@value interface Type1 {
call () -> (#self)
}
@value interface Type2 {}
concrete Type3 {
refines Type1
refines Type2
@type create () -> (Type3)
}
// ...
[Type1&Type2] value <- Type3.create()
value <- value.call()
#self
can be [Type1&Type2]
because Type3
→[Type1&Type2]
→Type1
. (Accidental consistency when substituting #self
in ccGetTypeFunction
in ProcedureContext
.)value
is Type3
, so that's what value.call()
actually returns. (Runtime type safety.)
The overall goal would be to have a meta-type that an
interface
can use to denote that the derived class is always required.The main use-case would be for things like iterators and builders, which always have at least one function that returns an object of the object's own type. Although these semantics are possible without a language change (see below), it would be inconvenient enough for people to not want to use it.
I think the semantics would be similar to this:
The main difference is that
#self
would automatically be assigned to the class doing therefines
ordefines
.Potential issues:
#self{ a, b, c }
? The main issue here is parsing.@type interface
s?#self
when overriding the type.all
. (This would work foroptional #self
or implementations thatfail
.)#self requires Foo
when declaring@value interface Foo
.#self defines Foo
when declaring@type interface Foo
.concrete Foo
requires some additional reasoning:#self requires Foo
, which also inheritsFoo
srefines
s.#self defines Bar
for every@type interface Bar
thatFoo
defines
.It seems like we can just parse it as a normal param, then do a dynamic substitution whenever reasoning about types is needed.
CompilerContext
, with the possible addition of a new function to perform substitution in the type for a new variable.ScopedFunction
.This also raises an interesting question about parameters-as-variable semantics: Should type declarations be treated as functions and type substitutions as function calls? (That is, from the perspective of compilation.)