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

fix(fast_check): infer literal types in const contexts #555

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 34 additions & 23 deletions src/fast_check/swc_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,28 +179,39 @@ pub fn ts_tuple_element(ts_type: TsType) -> TsTupleElement {
}
}

pub fn maybe_lit_to_ts_type_const(lit: &Lit) -> Option<TsType> {
match lit {
Lit::Str(lit_str) => Some(ts_lit_type(TsLit::Str(lit_str.clone()))),
Lit::Bool(lit_bool) => Some(ts_lit_type(TsLit::Bool(*lit_bool))),
Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
Lit::Num(lit_num) => Some(ts_lit_type(TsLit::Number(lit_num.clone()))),
Lit::BigInt(lit_bigint) => {
Some(ts_lit_type(TsLit::BigInt(lit_bigint.clone())))
}
Lit::Regex(_) => Some(regex_type()),
Lit::JSXText(_) => None,
}
}

pub fn maybe_lit_to_ts_type(lit: &Lit) -> Option<TsType> {
match lit {
Lit::Str(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)),
Lit::Bool(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword)),
Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
Lit::Num(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)),
Lit::BigInt(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword)),
Lit::Regex(_) => Some(regex_type()),
Lit::JSXText(_) => None,
pub enum DeclMutabilityKind {
Const,
Mutable,
}

pub fn maybe_lit_to_ts_type(
lit: &Lit,
decl_kind: DeclMutabilityKind,
) -> Option<TsType> {
match decl_kind {
DeclMutabilityKind::Const => match lit {
Lit::Str(lit_str) => Some(ts_lit_type(TsLit::Str(lit_str.clone()))),
Lit::Bool(lit_bool) => Some(ts_lit_type(TsLit::Bool(*lit_bool))),
Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
Lit::Num(lit_num) => Some(ts_lit_type(TsLit::Number(lit_num.clone()))),
Lit::BigInt(lit_bigint) => {
Some(ts_lit_type(TsLit::BigInt(lit_bigint.clone())))
}
Lit::Regex(_) => Some(regex_type()),
Lit::JSXText(_) => None,
},
DeclMutabilityKind::Mutable => match lit {
Lit::Str(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)),
Lit::Bool(_) => {
Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword))
}
Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
Lit::Num(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)),
Lit::BigInt(_) => {
Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword))
}
Lit::Regex(_) => Some(regex_type()),
Lit::JSXText(_) => None,
},
}
}
75 changes: 51 additions & 24 deletions src/fast_check/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use super::swc_helpers::is_void_type;
use super::swc_helpers::maybe_lit_to_ts_type;
use super::swc_helpers::new_ident;
use super::swc_helpers::ts_keyword_type;
use super::swc_helpers::DeclMutabilityKind;
use super::swc_helpers::ReturnStatementAnalysis;
use super::transform_dts::FastCheckDtsDiagnostic;
use super::transform_dts::FastCheckDtsTransformer;
Expand Down Expand Up @@ -858,14 +859,20 @@ impl<'a> FastCheckTransformer<'a> {
_ => None,
};
explicit_type_ann.or_else(|| {
self.maybe_infer_type_from_expr(&assign.right).map(
|type_ann| {
self
.maybe_infer_type_from_expr(
&assign.right,
match prop.readonly {
true => DeclMutabilityKind::Const,
false => DeclMutabilityKind::Mutable,
},
)
.map(|type_ann| {
Box::new(TsTypeAnn {
span: DUMMY_SP,
type_ann: Box::new(type_ann),
})
},
)
})
})
}
}
Expand Down Expand Up @@ -1006,10 +1013,15 @@ impl<'a> FastCheckTransformer<'a> {
return Ok(true);
}
if n.type_ann.is_none() {
let inferred_type = n
.value
.as_ref()
.and_then(|e| self.maybe_infer_type_from_expr(e));
let inferred_type = n.value.as_ref().and_then(|e| {
self.maybe_infer_type_from_expr(
e,
match n.readonly {
true => DeclMutabilityKind::Const,
false => DeclMutabilityKind::Mutable,
},
)
});
match inferred_type {
Some(t) => {
n.type_ann = Some(Box::new(TsTypeAnn {
Expand Down Expand Up @@ -1056,10 +1068,9 @@ impl<'a> FastCheckTransformer<'a> {
} else if let Some(type_ann) = n.type_ann.clone() {
type_ann
} else {
let inferred_type = n
.value
.as_ref()
.and_then(|e| self.maybe_infer_type_from_expr(e));
let inferred_type = n.value.as_ref().and_then(|e| {
self.maybe_infer_type_from_expr(e, DeclMutabilityKind::Mutable)
});
match inferred_type {
Some(t) => Box::new(TsTypeAnn {
span: DUMMY_SP,
Expand Down Expand Up @@ -1244,7 +1255,8 @@ impl<'a> FastCheckTransformer<'a> {
)?;
}
BlockStmtOrExpr::Expr(expr) => {
let inferred_type = self.maybe_infer_type_from_expr(expr);
let inferred_type =
self.maybe_infer_type_from_expr(expr, DeclMutabilityKind::Mutable);
match inferred_type {
Some(t) => {
let mut return_type = Box::new(t);
Expand Down Expand Up @@ -1370,7 +1382,10 @@ impl<'a> FastCheckTransformer<'a> {
Pat::Assign(assign) => match &mut *assign.left {
Pat::Ident(ident) => {
if ident.type_ann.is_none() {
let inferred_type = self.maybe_infer_type_from_expr(&assign.right);
let inferred_type = self.maybe_infer_type_from_expr(
&assign.right,
DeclMutabilityKind::Mutable,
);
match inferred_type {
Some(t) => {
ident.type_ann = Some(Box::new(TsTypeAnn {
Expand Down Expand Up @@ -1491,7 +1506,7 @@ impl<'a> FastCheckTransformer<'a> {
// don't need to do anything for ambient decls
if !is_ambient {
for decl in &mut n.decls {
self.transform_var_declarator(decl)?;
self.transform_var_declarator(n.kind, decl)?;
}
}

Expand All @@ -1500,15 +1515,23 @@ impl<'a> FastCheckTransformer<'a> {

fn transform_var_declarator(
&mut self,
decl_kind: VarDeclKind,
n: &mut VarDeclarator,
) -> Result<(), Vec<FastCheckDiagnostic>> {
match &mut n.name {
Pat::Ident(ident) => {
if ident.type_ann.is_none() {
let inferred_type = n
.init
.as_ref()
.and_then(|e| self.maybe_infer_type_from_expr(e));
let inferred_type = n.init.as_ref().and_then(|e| {
self.maybe_infer_type_from_expr(
e,
match decl_kind {
VarDeclKind::Var | VarDeclKind::Let => {
DeclMutabilityKind::Mutable
}
VarDeclKind::Const => DeclMutabilityKind::Const,
},
)
});
match inferred_type {
Some(t) => {
ident.type_ann = Some(Box::new(TsTypeAnn {
Expand Down Expand Up @@ -1719,8 +1742,8 @@ impl<'a> FastCheckTransformer<'a> {
}
}
is_leavable
},
Expr::Object(n) => {
},
Expr::Object(n) => {
let mut is_leavable = true;
for prop in &mut n.props {
is_leavable = match prop {
Expand Down Expand Up @@ -1817,11 +1840,15 @@ impl<'a> FastCheckTransformer<'a> {
Ok(is_leavable)
}

fn maybe_infer_type_from_expr(&self, expr: &Expr) -> Option<TsType> {
fn maybe_infer_type_from_expr(
&self,
expr: &Expr,
decl_kind: DeclMutabilityKind,
) -> Option<TsType> {
match expr {
Expr::TsTypeAssertion(n) => infer_simple_type_from_type(&n.type_ann),
Expr::TsAs(n) => infer_simple_type_from_type(&n.type_ann),
Expr::Lit(lit) => maybe_lit_to_ts_type(lit),
Expr::Lit(lit) => maybe_lit_to_ts_type(lit, decl_kind),
Expr::Call(call_expr) => {
if self.is_call_expr_symbol_create(call_expr) {
Some(TsType::TsTypeOperator(TsTypeOperator {
Expand All @@ -1837,7 +1864,7 @@ impl<'a> FastCheckTransformer<'a> {
None
}
}
Expr::Paren(n) => self.maybe_infer_type_from_expr(&n.expr),
Expr::Paren(n) => self.maybe_infer_type_from_expr(&n.expr, decl_kind),
Expr::This(_)
| Expr::Array(_)
| Expr::Object(_)
Expand Down
16 changes: 8 additions & 8 deletions src/fast_check/transform_dts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use crate::FastCheckDiagnosticRange;
use super::range_finder::ModulePublicRanges;
use super::swc_helpers::any_type_ann;
use super::swc_helpers::maybe_lit_to_ts_type;
use super::swc_helpers::maybe_lit_to_ts_type_const;
use super::swc_helpers::ts_readonly;
use super::swc_helpers::ts_tuple_element;
use super::swc_helpers::type_ann;
use super::swc_helpers::DeclMutabilityKind;

#[derive(Debug, Clone, thiserror::Error)]
pub enum FastCheckDtsDiagnostic {
Expand Down Expand Up @@ -419,13 +419,13 @@ impl<'a> FastCheckDtsTransformer<'a> {
members,
}))
}
Expr::Lit(lit) => {
if as_const {
maybe_lit_to_ts_type_const(&lit)
} else {
maybe_lit_to_ts_type(&lit)
}
}
Expr::Lit(lit) => maybe_lit_to_ts_type(
&lit,
match as_const {
true => DeclMutabilityKind::Const,
false => DeclMutabilityKind::Mutable,
},
),
Expr::TsConstAssertion(ts_const) => {
self.expr_to_ts_type(*ts_const.expr, true, true)
}
Expand Down
12 changes: 11 additions & 1 deletion tests/specs/graph/fast_check/class_properties.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export class Baz extends Bar {
override foo = "";
}

export class ReadonlyPropConstant {
readonly static value = "value";
}

# mod.ts
import 'jsr:@scope/a'

Expand Down Expand Up @@ -80,7 +84,7 @@ import 'jsr:@scope/a'
},
{
"kind": "esm",
"size": 867,
"size": 941,
"mediaType": "TypeScript",
"specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts"
}
Expand Down Expand Up @@ -122,6 +126,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts:
export class Baz extends Bar {
declare foo: string;
}
export class ReadonlyPropConstant {
declare static readonly value: "value";
}
--- DTS ---
export declare class RedBlackNode<T> extends BinarySearchNode<T> {
parent: RedBlackNode<T> | null;
Expand All @@ -144,3 +151,6 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts:
export declare class Baz extends Bar {
foo: string;
}
export declare class ReadonlyPropConstant {
static readonly value: "value";
}
4 changes: 2 additions & 2 deletions tests/specs/graph/fast_check/enums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts:
Value1 = "a",
Value2 = "b"
}
const value: number = {} as never;
const value: 10 = {} as never;
export enum EnumWithNonConstInits {
Value1 = new Public1().test,
Value2 = new Public2().asdf * value + NonExportedEnum.Value
Expand Down Expand Up @@ -145,7 +145,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts:
Value1 = "a",
Value2 = "b"
}
declare const value: number;
declare const value: 10;
export declare enum EnumWithNonConstInits {
Value1,
Value2
Expand Down
4 changes: 2 additions & 2 deletions tests/specs/graph/fast_check/vars.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts:
export const inferred9: [...string] = {} as never;
export const inferred10: (1 | 2n)[] = {} as never;
export const inferred11: (keyof string)[] = {} as never;
export const inferred12: number = {} as never;
export const inferred12: 1 = {} as never;
--- DTS ---
declare const public1: Public1;
export { public1 };
Expand Down Expand Up @@ -214,4 +214,4 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts:
export declare const inferred9: [...string];
export declare const inferred10: (1 | 2n)[];
export declare const inferred11: (keyof string)[];
export declare const inferred12: number;
export declare const inferred12: 1;
Loading