-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
Right now ts allow you create type aliases for primitives, which is nice but at the end of the day it's just a different name for the same type. It would be helpful to have a different way to alias scoped with in the module / namespace boundary. This would allow library author to export set of types that under the hood are aliases to the same primitive type, but for users of this library they are not, there for type checker will assist users & catch possible missuses.
Example
Consider example of using https://google.github.io/flatbuffers/ library. Conveniently it has a typescirpt generator which given following definition:
namespace Test;
table User {
name:string;
}
I am posting type definitions of the relevant bits inline below, but you are interested you can see generated typescript output and full definition for the flatbuffers library
declare namespace flatbuffers {
type Offset = number;
class Builder {
constructor(initial_size?: number);
createString(s: string|Uint8Array): Offset;
startObject(numfields: number): void;
endObject():Offset;
}
}
declare namespace Test {
class User {
static startUser(builder:flatbuffers.Builder):void;
static addName(builder:flatbuffers.Builder, nameOffset:flatbuffers.Offset):void;
static endUser(builder:flatbuffers.Builder):flatbuffers.Offset;
}
}
This is how user would create a Name
table:
const builder = new flatbuffers.Builder()
const name:number = builder.createString('gozala')
Test.User.startUser(builder)
Test.User.addName(builder, name)
const user:number = Test.User.endUser(builder)
As you may notice both written strings and tables (as in fact every other data strucutre in flatbuffers) are represented via integer (that is an offset with in the buffer). This unfortunately means type checker won't be able to assist user with errors like the following:
Test.User.startUser(builder)
Test.User.addName(builder, user) // <- user is not a pointer to written string but rather to a table
const other:number = Test.User.endUser(builder)
On one hand flatbuffers library could box everything to provide a more type checker friendly APIs but that would come at the memory and execution cost, and in certain cases that is not good compromise.
Proposal
Now with proposed opaque type aliases here is how above inlined type difinitions could look like (I use Opaque<number>
here but think of it as a placeholder for syntax you'd like to use instead):
declare namespace flatbuffers {
type StringToken = Opaque<number>;
type ObjectToken = Opaque<number>;
class Builder {
constructor(initial_size?: number);
createString(s: string|Uint8Array): StringToken;
startObject(numfields: number): void;
endObject():ObjectToken;
}
}
declare namespace Test {
type UserToken = Opaque<flatbuffers.ObjectToken>;
class User {
static startUser(builder:flatbuffers.Builder):void;
static addName(builder:flatbuffers.Builder, name:flatbuffers.StringToken):void;
static endUser(builder:flatbuffers.Builder):UserToken;
}
}
With that I would expect following behavior:
const builder = new flatbuffers.Builder()
const name = builder.createString('gozala')
Test.User.startUser(builder)
Test.User.addName(builder, 3) // <- Argument of type '3' is not assignable to parameter of type 'StringToken'.
Test.User.addName(builder, name)
const user = Test.User.endUser(builder)
Test.User.startUser(builder)
Test.User.addName(builder, user) // <- Argument of type 'UserToken' is not assignable to parameter of type 'StringToken'.