Skip to content

Commit

Permalink
implement optional method calls x?.y()
Browse files Browse the repository at this point in the history
  • Loading branch information
y21 committed Dec 28, 2024
1 parent 6b6a214 commit 5047f0f
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 29 deletions.
3 changes: 2 additions & 1 deletion crates/dash_compiler/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ impl InstructionBuilder<'_, '_> {
self.lower_function_call_common(span, target_span, has_this, kind, fc.arguments)
}

fn lower_function_call_common(
/// Lowers parts of a function call, assuming that the receiver is on the stack
pub fn lower_function_call_common(
&mut self,
span: Span,
target_span: Span,
Expand Down
60 changes: 53 additions & 7 deletions crates/dash_compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use dash_middle::compiler::constant::{Buffer, ConstantPool, Function, NumberCons
use dash_middle::compiler::external::External;
use dash_middle::compiler::instruction::{AssignKind, Instruction, IntrinsicOperation};
use dash_middle::compiler::scope::{CompileValueType, LimitExceededError, Local, ScopeGraph};
use dash_middle::compiler::{CompileResult, DebugSymbols, StaticImportKind};
use dash_middle::compiler::{CompileResult, DebugSymbols, FunctionCallKind, StaticImportKind};
use dash_middle::interner::{StringInterner, Symbol, sym};
use dash_middle::lexer::token::TokenType;
use dash_middle::parser::error::Error;
Expand Down Expand Up @@ -1298,21 +1298,67 @@ impl Visitor<Result<(), Error>> for FunctionCompiler<'_> {
chain: OptionalChainingExpression,
) -> Result<(), Error> {
let mut ib = InstructionBuilder::new(self);
ib.accept_expr(*chain.base)?;

let preserve_this_for_base = matches!(
chain.components.first(),
Some(OptionalChainingComponent::Call(_) | OptionalChainingComponent::Construct(_))
);

if let Expr {
span,
kind: ExprKind::PropertyAccess(p),
} = *chain.base
{
ib.visit_property_access_expr(span, p, preserve_this_for_base)?;
} else {
ib.accept_expr(*chain.base)?;
}

ib.build_jmpnullishnp(Label::IfBranch { branch_id: 0 }, true);

let mut prev_property_access = preserve_this_for_base;
for component in chain.components {
let is_property_access = matches!(
component,
OptionalChainingComponent::Dyn { .. } | OptionalChainingComponent::Ident { .. }
);

match component {
OptionalChainingComponent::Ident(ident) => {
ib.build_static_prop_access(ident, false)
OptionalChainingComponent::Ident {
property,
preserve_this,
} => {
ib.build_static_prop_access(property, preserve_this)
.map_err(|_| Error::ConstantPoolLimitExceeded(span))?;
}
OptionalChainingComponent::Dyn(expr) => {
ib.accept_expr(expr)?;
ib.build_dynamic_prop_access(false);
OptionalChainingComponent::Dyn {
property,
preserve_this,
} => {
ib.accept_expr(property)?;
ib.build_dynamic_prop_access(preserve_this);
}
OptionalChainingComponent::Call(arguments) => {
ib.lower_function_call_common(
span,
span,
prev_property_access,
FunctionCallKind::Function,
arguments,
)?;
}
OptionalChainingComponent::Construct(arguments) => {
ib.lower_function_call_common(
span,
span,
prev_property_access,
FunctionCallKind::Constructor,
arguments,
)?;
}
}

prev_property_access = is_property_access;
}
ib.build_jmp(Label::IfEnd, true);

Expand Down
2 changes: 1 addition & 1 deletion crates/dash_lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ impl<'a, 'interner> Lexer<'a, 'interner> {
b'?' => self.conditional_token(TokenType::Conditional, &[
("?=", TokenType::LogicalNullishAssignment),
(".[", TokenType::OptionalSquareBrace),
(".(", TokenType::OptionalCall),
(".(", TokenType::OptionalLeftParen),
("?", TokenType::NullishCoalescing),
(".", TokenType::OptionalDot),
]),
Expand Down
2 changes: 1 addition & 1 deletion crates/dash_middle/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ pub enum TokenType {
OptionalSquareBrace,

#[display(fmt = "?.(")]
OptionalCall,
OptionalLeftParen,

#[display(fmt = "for")]
For,
Expand Down
15 changes: 11 additions & 4 deletions crates/dash_middle/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ impl fmt::Display for OptionalChainingExpression {
f.write_str("?")?;
for component in &self.components {
match component {
OptionalChainingComponent::Ident(symbol) => write!(f, ".{symbol}")?,
OptionalChainingComponent::Dyn(expr) => write!(f, "[{expr}]")?,
OptionalChainingComponent::Ident { property, .. } => write!(f, ".{property}")?,
OptionalChainingComponent::Dyn { property, .. } => write!(f, "[{property}]")?,
OptionalChainingComponent::Call(args) | OptionalChainingComponent::Construct(args) => {
fmt_list(f, args, ", ")?
}
}
}
Ok(())
Expand All @@ -89,9 +92,13 @@ impl fmt::Display for OptionalChainingExpression {
#[derive(Debug, Clone)]
pub enum OptionalChainingComponent {
/// ?.x
Ident(Symbol),
Ident { property: Symbol, preserve_this: bool },
/// ?.[expr]
Dyn(Expr),
Dyn { property: Expr, preserve_this: bool },
/// ?.(args)
Call(Vec<CallArgumentKind>),
/// new ...?.(args)
Construct(Vec<CallArgumentKind>),
}

#[derive(Debug, Clone, Display)]
Expand Down
32 changes: 29 additions & 3 deletions crates/dash_optimizer/src/type_infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use dash_middle::lexer::token::TokenType;
use dash_middle::parser::expr::{
ArrayLiteral, ArrayMemberKind, AssignmentExpr, AssignmentTarget, BinaryExpr, CallArgumentKind, ConditionalExpr,
Expr, ExprKind, FunctionCall, GroupingExpr, LiteralExpr, ObjectLiteral, ObjectMemberKind,
OptionalChainingExpression, PropertyAccessExpr, UnaryExpr,
OptionalChainingComponent, OptionalChainingExpression, PropertyAccessExpr, UnaryExpr,
};
use dash_middle::parser::statement::{
Binding, BlockStatement, Class, ClassMemberKey, ClassMemberValue, DoWhileLoop, ExportKind, ForInLoop, ForLoop,
Expand Down Expand Up @@ -394,7 +394,7 @@ impl<'s> TypeInferCtx<'s> {
ExprKind::Class(class) => self.visit_class_expression(class),
ExprKind::Array(expr) => self.visit_array_expression(expr),
ExprKind::Object(expr) => self.visit_object_expression(expr),
ExprKind::Chaining(OptionalChainingExpression { base, components: _ }) => self.visit(base),
ExprKind::Chaining(chain) => self.visit_chaining_expression(chain),
ExprKind::Compiled(..) => None,
ExprKind::Empty => None,
ExprKind::NewTarget => None,
Expand All @@ -405,6 +405,28 @@ impl<'s> TypeInferCtx<'s> {
}
}

pub fn visit_chaining_expression(
&mut self,
OptionalChainingExpression { base, components }: &OptionalChainingExpression,
) -> Option<CompileValueType> {
self.visit(base);
for component in components {
match component {
OptionalChainingComponent::Ident {
preserve_this: _,
property: _,
} => {}
OptionalChainingComponent::Dyn {
property,
preserve_this: _,
} => drop(self.visit(property)),
OptionalChainingComponent::Call(arguments) => self.visit_call_arguments(arguments),
OptionalChainingComponent::Construct(arguments) => self.visit_call_arguments(arguments),
}
}
None
}

pub fn visit_binary_expression(
&mut self,
BinaryExpr { left, right, operator }: &BinaryExpr,
Expand Down Expand Up @@ -550,13 +572,17 @@ impl<'s> TypeInferCtx<'s> {
FunctionCall { target, arguments, .. }: &FunctionCall,
) -> Option<CompileValueType> {
self.visit(target);
self.visit_call_arguments(arguments);
None
}

pub fn visit_call_arguments(&mut self, arguments: &[CallArgumentKind]) {
for argument in arguments {
match argument {
CallArgumentKind::Normal(expr) => drop(self.visit(expr)),
CallArgumentKind::Spread(expr) => drop(self.visit(expr)),
}
}
None
}

pub fn visit_conditional_expression(
Expand Down
68 changes: 56 additions & 12 deletions crates/dash_parser/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ impl Parser<'_, '_> {
TokenType::LeftSquareBrace,
TokenType::OptionalDot,
TokenType::OptionalSquareBrace,
TokenType::OptionalLeftParen,
]),
false,
)
Expand All @@ -355,7 +356,8 @@ impl Parser<'_, '_> {
let previous = self.previous()?.ty;

match previous {
TokenType::LeftParen => {
TokenType::LeftParen | TokenType::OptionalLeftParen => {
let is_optional = previous == TokenType::OptionalLeftParen;
let mut arguments = Vec::new();

// Disassociate any new expressions in arguments such that `new x(() => x());`
Expand All @@ -380,20 +382,53 @@ impl Parser<'_, '_> {
self.new_level_stack.dec_level().expect("Missing `new` level stack");
}

expr = Expr {
span: expr.span.to(self.previous()?.span),
kind: ExprKind::function_call(expr, arguments, is_constructor_call),
};
if is_optional {
let component = if is_constructor_call {
OptionalChainingComponent::Construct(arguments)
} else {
OptionalChainingComponent::Call(arguments)
};
if let ExprKind::Chaining(c) = &mut expr.kind {
// If this is a method call (i.e. the previous chain component is a property access,
// we need to preserve its `this` binding)
if let Some(last) = c.components.last_mut() {
match last {
OptionalChainingComponent::Ident { preserve_this, .. }
| OptionalChainingComponent::Dyn { preserve_this, .. } => *preserve_this = true,
OptionalChainingComponent::Call(_) | OptionalChainingComponent::Construct(_) => {}
}
}

c.components.push(component);
expr.span = expr.span.to(self.previous()?.span);
} else {
expr = Expr {
span: expr.span.to(self.previous()?.span),
kind: ExprKind::Chaining(OptionalChainingExpression {
base: Box::new(expr),
components: vec![component],
}),
}
}
} else {
expr = Expr {
span: expr.span.to(self.previous()?.span),
kind: ExprKind::function_call(expr, arguments, is_constructor_call),
};
}
}
TokenType::Dot => {
let ident = self.expect_identifier_or_reserved_kw(true)?;
let property = self.expect_identifier_or_reserved_kw(true)?;
if let ExprKind::Chaining(chain) = &mut expr.kind {
chain.components.push(OptionalChainingComponent::Ident(ident));
chain.components.push(OptionalChainingComponent::Ident {
property,
preserve_this: false,
});
expr.span = expr.span.to(self.previous()?.span);
} else {
let property = Expr {
span: self.previous()?.span,
kind: ExprKind::identifier(ident),
kind: ExprKind::identifier(property),
};
expr = Expr {
span: expr.span.to(property.span),
Expand All @@ -406,11 +441,14 @@ impl Parser<'_, '_> {
}
}
TokenType::OptionalDot => {
let ident = self.expect_identifier_or_reserved_kw(true)?;
let property = self.expect_identifier_or_reserved_kw(true)?;
let span = expr.span.to(self.previous()?.span);
let chain = OptionalChainingExpression {
base: Box::new(expr),
components: vec![OptionalChainingComponent::Ident(ident)],
components: vec![OptionalChainingComponent::Ident {
property,
preserve_this: false,
}],
};
expr = Expr {
span,
Expand All @@ -421,7 +459,10 @@ impl Parser<'_, '_> {
let property = self.parse_expression()?;
self.eat(TokenType::RightSquareBrace, true)?;
if let ExprKind::Chaining(c) = &mut expr.kind {
c.components.push(OptionalChainingComponent::Dyn(property));
c.components.push(OptionalChainingComponent::Dyn {
property,
preserve_this: false,
});
expr.span = expr.span.to(self.previous()?.span);
} else {
expr = Expr {
Expand All @@ -438,7 +479,10 @@ impl Parser<'_, '_> {
span,
kind: ExprKind::Chaining(OptionalChainingExpression {
base: Box::new(expr),
components: vec![OptionalChainingComponent::Dyn(property)],
components: vec![OptionalChainingComponent::Dyn {
property,
preserve_this: false,
}],
}),
};
}
Expand Down

0 comments on commit 5047f0f

Please sign in to comment.