Skip to content

Commit

Permalink
Add Effect:TypeOf and give typeofs user-defineable names (#68842)
Browse files Browse the repository at this point in the history
This is effectively an optimization to make Webpack UMD modules work
better by short circuiting and not even looking at the unused branches
(e.g. AMD)

Closes PACK-2983
  • Loading branch information
mischnic authored Aug 19, 2024
1 parent 68a7128 commit 6266149
Show file tree
Hide file tree
Showing 22 changed files with 8,068 additions and 5,849 deletions.
10 changes: 7 additions & 3 deletions crates/next-core/src/next_client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use turbopack_browser::{react_refresh::assert_can_resolve_react_refresh, Browser
use turbopack_core::{
chunk::ChunkingContext,
compile_time_info::{
CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, FreeVarReference,
FreeVarReferences,
CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, DefineableNameSegment,
FreeVarReference, FreeVarReferences,
},
condition::ContextCondition,
environment::{BrowserEnvironment, Environment, ExecutionEnvironment},
Expand Down Expand Up @@ -68,7 +68,11 @@ fn defines(define_env: &IndexMap<RcStr, RcStr>) -> CompileTimeDefines {

for (k, v) in define_env {
defines
.entry(k.split('.').map(|s| s.into()).collect::<Vec<RcStr>>())
.entry(
k.split('.')
.map(|s| DefineableNameSegment::Name(s.into()))
.collect::<Vec<_>>(),
)
.or_insert_with(|| {
let val = serde_json::from_str(v);
match val {
Expand Down
10 changes: 7 additions & 3 deletions crates/next-core/src/next_edge/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use turbopack_browser::BrowserChunkingContext;
use turbopack_core::{
chunk::ChunkingContext,
compile_time_info::{
CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, FreeVarReference,
FreeVarReferences,
CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, DefineableNameSegment,
FreeVarReference, FreeVarReferences,
},
environment::{EdgeWorkerEnvironment, Environment, ExecutionEnvironment},
free_var_references,
Expand All @@ -34,7 +34,11 @@ fn defines(define_env: &IndexMap<RcStr, RcStr>) -> CompileTimeDefines {

for (k, v) in define_env {
defines
.entry(k.split('.').map(|s| s.into()).collect::<Vec<RcStr>>())
.entry(
k.split('.')
.map(|s| DefineableNameSegment::Name(s.into()))
.collect::<Vec<_>>(),
)
.or_insert_with(|| {
let val = serde_json::from_str(v);
match val {
Expand Down
9 changes: 7 additions & 2 deletions crates/next-core/src/next_server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use turbopack::{
};
use turbopack_core::{
compile_time_info::{
CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, FreeVarReferences,
CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, DefineableNameSegment,
FreeVarReferences,
},
condition::ContextCondition,
environment::{Environment, ExecutionEnvironment, NodeJsEnvironment, RuntimeVersions},
Expand Down Expand Up @@ -316,7 +317,11 @@ fn defines(define_env: &IndexMap<RcStr, RcStr>) -> CompileTimeDefines {

for (k, v) in define_env {
defines
.entry(k.split('.').map(|s| s.into()).collect::<Vec<RcStr>>())
.entry(
k.split('.')
.map(|s| DefineableNameSegment::Name(s.into()))
.collect::<Vec<_>>(),
)
.or_insert_with(|| {
let val = serde_json::from_str(v);
match val {
Expand Down
163 changes: 139 additions & 24 deletions turbopack/crates/turbopack-core/src/compile_time_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,40 @@ use turbo_tasks_fs::FileSystemPath;

use crate::environment::Environment;

#[macro_export]
macro_rules! definable_name_map_pattern_internal {
($name:ident) => {
[stringify!($name).into()]
};
($name:ident typeof) => {
[stringify!($name).into(), $crate::compile_time_info::DefineableNameSegment::TypeOf]
};
// Entry point for non-recursive calls
($name:ident . $($more:ident).+ typeof) => {
$crate::definable_name_map_pattern_internal!($($more).+ typeof, [stringify!($name).into()])
};
($name:ident . $($more:ident).+) => {
$crate::definable_name_map_pattern_internal!($($more).+, [stringify!($name).into()])
};
// Pop first ident and push to end of array: (id, ..., [...]) => (..., [..., id])
($name:ident, [$($array:expr),+]) => {
[$($array),+, stringify!($name).into()]
};
($name:ident . $($more:ident).+, [$($array:expr),+]) => {
$crate::definable_name_map_pattern_internal!($($more).+, [$($array),+, stringify!($name).into()])
};
($name:ident typeof, [$($array:expr),+]) => {
[$($array),+, stringify!($name).into(), $crate::compile_time_info::DefineableNameSegment::TypeOf]
};
($name:ident . $($more:ident).+ typeof, [$($array:expr),+]) => {
$crate::definable_name_map_pattern_internal!($($more).+ typeof, [$($array),+, stringify!($name).into()])
};
}

// TODO stringify split map collect could be optimized with a marco
#[macro_export]
macro_rules! definable_name_map_internal {
// Allow spreading a map: free_var_references!(..xy.into_iter(), FOO = "bar")
($map:ident, .. $value:expr) => {
for (key, value) in $value {
$map.insert(
Expand All @@ -16,38 +47,33 @@ macro_rules! definable_name_map_internal {
);
}
};
($map:ident, $($name:ident).+ = $value:expr) => {
($map:ident, .. $value:expr, $($more:tt)+) => {
$crate::definable_name_map_internal!($map, .. $value);
$crate::definable_name_map_internal!($map, $($more)+);
};
// Base case: a single entry
($map:ident, typeof $($name:ident).+ = $value:expr $(,)?) => {
$map.insert(
$crate::definable_name_map_internal!($($name).+).into(),
$crate::definable_name_map_pattern_internal!($($name).+ typeof).into(),
$value.into()
);
};
($map:ident, $($name:ident).+ = $value:expr,) => {
($map:ident, $($name:ident).+ = $value:expr $(,)?) => {
$map.insert(
$crate::definable_name_map_internal!($($name).+).into(),
$crate::definable_name_map_pattern_internal!($($name).+).into(),
$value.into()
);
};
($map:ident, $($name:ident).+ = $value:expr, $($more:tt)+) => {
$crate::definable_name_map_internal!($map, $($name).+ = $value);
// Recursion: split off first entry
($map:ident, typeof $($name:ident).+ = $value:expr, $($more:tt)+) => {
$crate::definable_name_map_internal!($map, typeof $($name).+ = $value);
$crate::definable_name_map_internal!($map, $($more)+);
};
($map:ident, .. $value:expr, $($more:tt)+) => {
$crate::definable_name_map_internal!($map, .. $value);
($map:ident, $($name:ident).+ = $value:expr, $($more:tt)+) => {
$crate::definable_name_map_internal!($map, $($name).+ = $value);
$crate::definable_name_map_internal!($map, $($more)+);
};
($name:ident) => {
[stringify!($name).into()]
};
($name:ident . $($more:ident).+) => {
$crate::definable_name_map_internal!($($more).+, [stringify!($name).into()])
};
($name:ident, [$($array:expr),+]) => {
[$($array),+, stringify!($name).into()]
};
($name:ident . $($more:ident).+, [$($array:expr),+]) => {
$crate::definable_name_map_internal!($($more).+, [$($array),+, stringify!($name).into()])
};

}

#[macro_export]
Expand Down Expand Up @@ -112,13 +138,38 @@ impl From<serde_json::Value> for CompileTimeDefineValue {
}
}

#[turbo_tasks::value(serialization = "auto_for_input")]
#[derive(Debug, Clone, Hash)]
pub enum DefineableNameSegment {
Name(RcStr),
TypeOf,
}

impl From<RcStr> for DefineableNameSegment {
fn from(value: RcStr) -> Self {
DefineableNameSegment::Name(value)
}
}

impl From<&str> for DefineableNameSegment {
fn from(value: &str) -> Self {
DefineableNameSegment::Name(value.into())
}
}

impl From<String> for DefineableNameSegment {
fn from(value: String) -> Self {
DefineableNameSegment::Name(value.into())
}
}

#[turbo_tasks::value(transparent)]
#[derive(Debug, Clone)]
pub struct CompileTimeDefines(pub IndexMap<Vec<RcStr>, CompileTimeDefineValue>);
pub struct CompileTimeDefines(pub IndexMap<Vec<DefineableNameSegment>, CompileTimeDefineValue>);

impl IntoIterator for CompileTimeDefines {
type Item = (Vec<RcStr>, CompileTimeDefineValue);
type IntoIter = indexmap::map::IntoIter<Vec<RcStr>, CompileTimeDefineValue>;
type Item = (Vec<DefineableNameSegment>, CompileTimeDefineValue);
type IntoIter = indexmap::map::IntoIter<Vec<DefineableNameSegment>, CompileTimeDefineValue>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
Expand Down Expand Up @@ -171,7 +222,7 @@ impl From<CompileTimeDefineValue> for FreeVarReference {

#[turbo_tasks::value(transparent)]
#[derive(Debug, Clone)]
pub struct FreeVarReferences(pub IndexMap<Vec<RcStr>, FreeVarReference>);
pub struct FreeVarReferences(pub IndexMap<Vec<DefineableNameSegment>, FreeVarReference>);

#[turbo_tasks::value_impl]
impl FreeVarReferences {
Expand Down Expand Up @@ -248,3 +299,67 @@ impl CompileTimeInfoBuilder {
self.build().cell()
}
}

#[cfg(test)]
mod test {
use indexmap::IndexMap;

use crate::compile_time_info::{DefineableNameSegment, FreeVarReference, FreeVarReferences};

#[test]
fn macro_parser() {
assert_eq!(
free_var_references!(
FOO = "bar",
FOO = false,
Buffer = FreeVarReference::EcmaScriptModule {
request: "node:buffer".into(),
lookup_path: None,
export: Some("Buffer".into()),
},
),
FreeVarReferences(IndexMap::from_iter(vec![
(vec!["FOO".into()], FreeVarReference::Value("bar".into())),
(vec!["FOO".into()], FreeVarReference::Value(false.into())),
(
vec!["Buffer".into()],
FreeVarReference::EcmaScriptModule {
request: "node:buffer".into(),
lookup_path: None,
export: Some("Buffer".into()),
}
),
]))
);
}

#[test]
fn macro_parser_typeof() {
assert_eq!(
free_var_references!(
typeof x = "a",
typeof x.y = "b",
typeof x.y.z = "c"
),
FreeVarReferences(IndexMap::from_iter(vec![
(
vec!["x".into(), DefineableNameSegment::TypeOf],
FreeVarReference::Value("a".into())
),
(
vec!["x".into(), "y".into(), DefineableNameSegment::TypeOf],
FreeVarReference::Value("b".into())
),
(
vec![
"x".into(),
"y".into(),
"z".into(),
DefineableNameSegment::TypeOf
],
FreeVarReference::Value("c".into())
)
]))
);
}
}
39 changes: 38 additions & 1 deletion turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
};

use swc_core::{
atoms::Atom,
common::{pass::AstNodePath, Mark, Span, Spanned, SyntaxContext, GLOBALS},
ecma::{
ast::*,
Expand All @@ -15,7 +16,10 @@ use swc_core::{
use turbo_tasks::{RcStr, Vc};
use turbopack_core::source::Source;

use super::{ConstantNumber, ConstantValue, ImportMap, JsValue, ObjectPart, WellKnownFunctionKind};
use super::{
is_unresolved_id, ConstantNumber, ConstantValue, ImportMap, JsValue, ObjectPart,
WellKnownFunctionKind,
};
use crate::{
analyzer::is_unresolved,
utils::{unparen, AstPathRange},
Expand Down Expand Up @@ -172,6 +176,12 @@ pub enum Effect {
span: Span,
in_try: bool,
},
/// A typeof expression
TypeOf {
arg: JsValue,
ast_path: Vec<AstParentKind>,
span: Span,
},
// TODO ImportMeta should be replaced with Member
/// A reference to `import.meta`.
ImportMeta {
Expand Down Expand Up @@ -223,6 +233,9 @@ impl Effect {
var.normalize();
}
Effect::ImportedBinding { .. } => {}
Effect::TypeOf { arg, .. } => {
arg.normalize();
}
Effect::ImportMeta { .. } => {}
Effect::Url { input, .. } => {
input.normalize();
Expand All @@ -235,6 +248,8 @@ impl Effect {
#[derive(Debug)]
pub struct VarGraph {
pub values: HashMap<Id, JsValue>,
/// Map FreeVar names to their Id to facilitate lookups into [values]
pub free_var_ids: HashMap<Atom, Id>,

pub effects: Vec<Effect>,
}
Expand All @@ -255,6 +270,7 @@ impl VarGraph {
pub fn create_graph(m: &Program, eval_context: &EvalContext) -> VarGraph {
let mut graph = VarGraph {
values: Default::default(),
free_var_ids: Default::default(),
effects: Default::default(),
};

Expand Down Expand Up @@ -740,6 +756,10 @@ pub fn is_in_try(ast_path: &AstNodePath<AstParentNodeRef<'_>>) -> bool {

impl Analyzer<'_> {
fn add_value(&mut self, id: Id, value: JsValue) {
if is_unresolved_id(&id, self.eval_context.unresolved_mark) {
self.data.free_var_ids.insert(id.0.clone(), id.clone());
}

if let Some(prev) = self.data.values.get_mut(&id) {
prev.add_alt(value);
} else {
Expand Down Expand Up @@ -1972,6 +1992,23 @@ impl VisitAstPath for Analyzer<'_> {
}
}
}

fn visit_unary_expr<'ast: 'r, 'r>(
&mut self,
n: &'ast UnaryExpr,
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
) {
if n.op == UnaryOp::TypeOf {
let arg_value = self.eval_context.eval(&n.arg);
self.add_effect(Effect::TypeOf {
arg: arg_value,
ast_path: as_parent_path(ast_path),
span: n.span(),
});
}

n.visit_children_with_path(self, ast_path);
}
}

impl<'a> Analyzer<'a> {
Expand Down
Loading

0 comments on commit 6266149

Please sign in to comment.