有没有办法强制 class 包含特定的枚举值(最好是内联的)?
Is there a way to enforce that a class contains a specific Enum value (preferably inline)?
我目前正在使用 TypeScript 编写编译器,我有一个枚举来表示令牌类型,class 用于实际令牌:
enum TokenType {
String,
Integer,
Float,
Identifier,
// ... elided
}
class Token {
type: TokenType
lexeme: string
lineNo: number
columnNo: number
constructor(
type: TokenType,
lexeme: string,
lineNo: number,
columnNo: number
) {
this.type = type
this.lexeme = lexeme
this.lineNo = lineNo
this.columnNo = columnNo
}
toString(): string {
return (
'Token{' +
[this.type, this.lexeme, this.lineNo, this.columnNo].join(',') +
'}'
)
}
}
在我的 AST 节点类型中,我想指定令牌持有特定类型,例如在 FunctionDeclaration
类型中:
type FunctionDeclaration = {
ident: Token with type = TokenType.identifier
// ^ Imaginary syntax, but this is what I'm trying to do
}
我试过使用 extend
比如:
interface IdentifierToken extends Token {
type: TokenType.Identifier
}
但是,这让我将 new Token(TokenType.Identifier, ...)
转换为 IdentifierToken
,即使令牌的类型是 TokenType.Identifier
.
此外,我宁愿不必为所有不同的 TokenTypes 声明新的单独类型(因为有 ~25 个)。那么,是否可以采用内联方式强制执行 class 属性的值?
按照现在的定义,Token#type
可以在 运行 时发生变化,因此无法在编译时断言在哪些地方使用了哪种令牌类型。您的选择包括:
- 切换到进行运行时间检查。例如,当构造
FunctionDeclaration
时(或者,如果它是一个普通对象,只要它被方法接受),在 运行 时间检查其 ident
标记的类型。
- 将
Token
设为 abstract
class 并将 type
设为 abstract readonly
字段。然后为每个标记类型(class IdentifierToken
、class FloatToken
等)创建 subclasses,将其固定为特定值(readonly type = TokenType.Identifier
、readonly type = TokenType.Float
等) .).请注意,这与可以在构造函数参数中自由分配的非只读字段不同。当您需要 input/output 解析实际类型的数据时(例如 Integer
和 Float
的 number
),拥有这些子classes 也可能会有所帮助.
- 如果您无论如何都要有 subclasses,也许要问您是否需要
type
字段。这种字段仍然可以在 class 层次结构中使用,但请记住,您现在还可以使用 instanceof
检查,更不用说每个子 class 中的简单覆盖行为了。
所以是的,在漫长的 运行 中,制作那些 ~25 classes(最好是真实的 classes 而不仅仅是接口)将会变得更加清晰。如果您有那么多令牌类型,并且不同类型需要在特定上下文中表现不同,那么这就是您的代码的必要复杂程度。
您可能需要考虑将 Token
设为 generic class,其类型参数对应于您正在使用的 TokenType
的特定子类型:
class Token<T extends TokenType = TokenType> {
type: T
lexeme: string
lineNo: number
columnNo: number
constructor(
type: T,
lexeme: string,
lineNo: number,
columnNo: number
) {
this.type = type
this.lexeme = lexeme
this.lineNo = lineNo
this.columnNo = columnNo
}
}
然后你可以很容易地将“Token
与 type
等于 XXX
称为 Token<XXX>
:
type FunctionDeclaration = {
ident: Token<TokenType.Identifier>
}
此外,当您使用 Token
构造函数时,编译器将根据构造参数推断 T
:
const identifierToken = new Token(TokenType.Identifier, "", 1, 2);
// const identifierToken: Token<TokenType.Identifier>
const f: FunctionDeclaration = { ident: identifierToken }; // okay
const floatToken = new Token(TokenType.Float, "", 3, 4);
// const floatToken: Token<TokenType.Float>
const g: FunctionDeclaration = { ident: floatToken }; // error!
// Type 'Token<TokenType.Float>' is not assignable to type 'Token<TokenType.Identifier>'.
我目前正在使用 TypeScript 编写编译器,我有一个枚举来表示令牌类型,class 用于实际令牌:
enum TokenType {
String,
Integer,
Float,
Identifier,
// ... elided
}
class Token {
type: TokenType
lexeme: string
lineNo: number
columnNo: number
constructor(
type: TokenType,
lexeme: string,
lineNo: number,
columnNo: number
) {
this.type = type
this.lexeme = lexeme
this.lineNo = lineNo
this.columnNo = columnNo
}
toString(): string {
return (
'Token{' +
[this.type, this.lexeme, this.lineNo, this.columnNo].join(',') +
'}'
)
}
}
在我的 AST 节点类型中,我想指定令牌持有特定类型,例如在 FunctionDeclaration
类型中:
type FunctionDeclaration = {
ident: Token with type = TokenType.identifier
// ^ Imaginary syntax, but this is what I'm trying to do
}
我试过使用 extend
比如:
interface IdentifierToken extends Token {
type: TokenType.Identifier
}
但是,这让我将 new Token(TokenType.Identifier, ...)
转换为 IdentifierToken
,即使令牌的类型是 TokenType.Identifier
.
此外,我宁愿不必为所有不同的 TokenTypes 声明新的单独类型(因为有 ~25 个)。那么,是否可以采用内联方式强制执行 class 属性的值?
按照现在的定义,Token#type
可以在 运行 时发生变化,因此无法在编译时断言在哪些地方使用了哪种令牌类型。您的选择包括:
- 切换到进行运行时间检查。例如,当构造
FunctionDeclaration
时(或者,如果它是一个普通对象,只要它被方法接受),在 运行 时间检查其ident
标记的类型。 - 将
Token
设为abstract
class 并将type
设为abstract readonly
字段。然后为每个标记类型(class IdentifierToken
、class FloatToken
等)创建 subclasses,将其固定为特定值(readonly type = TokenType.Identifier
、readonly type = TokenType.Float
等) .).请注意,这与可以在构造函数参数中自由分配的非只读字段不同。当您需要 input/output 解析实际类型的数据时(例如Integer
和Float
的number
),拥有这些子classes 也可能会有所帮助. - 如果您无论如何都要有 subclasses,也许要问您是否需要
type
字段。这种字段仍然可以在 class 层次结构中使用,但请记住,您现在还可以使用instanceof
检查,更不用说每个子 class 中的简单覆盖行为了。
所以是的,在漫长的 运行 中,制作那些 ~25 classes(最好是真实的 classes 而不仅仅是接口)将会变得更加清晰。如果您有那么多令牌类型,并且不同类型需要在特定上下文中表现不同,那么这就是您的代码的必要复杂程度。
您可能需要考虑将 Token
设为 generic class,其类型参数对应于您正在使用的 TokenType
的特定子类型:
class Token<T extends TokenType = TokenType> {
type: T
lexeme: string
lineNo: number
columnNo: number
constructor(
type: T,
lexeme: string,
lineNo: number,
columnNo: number
) {
this.type = type
this.lexeme = lexeme
this.lineNo = lineNo
this.columnNo = columnNo
}
}
然后你可以很容易地将“Token
与 type
等于 XXX
称为 Token<XXX>
:
type FunctionDeclaration = {
ident: Token<TokenType.Identifier>
}
此外,当您使用 Token
构造函数时,编译器将根据构造参数推断 T
:
const identifierToken = new Token(TokenType.Identifier, "", 1, 2);
// const identifierToken: Token<TokenType.Identifier>
const f: FunctionDeclaration = { ident: identifierToken }; // okay
const floatToken = new Token(TokenType.Float, "", 3, 4);
// const floatToken: Token<TokenType.Float>
const g: FunctionDeclaration = { ident: floatToken }; // error!
// Type 'Token<TokenType.Float>' is not assignable to type 'Token<TokenType.Identifier>'.