Skip to content
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
21 changes: 20 additions & 1 deletion crates/perry-hir/src/monomorph/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,26 @@ fn collect_instantiations_in_expr(
};

if let Some(ta) = resolved_type_args {
ctx.request_class_specialization(class_name, ta);
// Never monomorphize a class instantiation whose type
// argument is still an unresolved type variable, e.g.
// `new Observable<R>()` inside `lift<R>()`. TypeScript
// class generics are erased at runtime, so `new C<R>()`
// must construct the SAME class `C` as `new C()`.
// Specializing on an unknown type var instead produced a
// bogus `C$R` class whose instances are NOT `instanceof
// C` and do not inherit `C`'s prototype (e.g. rxjs's
// `Observable.prototype[Symbol.observable]`). That broke
// rxjs `innerFrom`/`from` ObservableInput detection —
// every NestJS interceptor (`next.handle().pipe(...)`)
// 500'd because the piped Observable failed both the
// `input instanceof Observable` and `isInteropObservable`
// checks. Erase the type args and construct the base class.
if !ta
.iter()
.any(crate::monomorph::infer::type_contains_type_var)
{
ctx.request_class_specialization(class_name, ta);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,22 @@ pub extern "C" fn js_object_get_field_by_name(
}
return JSValue::from_bits(value.to_bits());
}
if class_id != 0 && class_has_own_method(class_id, name) {
// Instance (prototype) methods must only resolve when reading
// off the prototype ref (`C.prototype.m`), NOT off the class ref
// itself (`C.m`). In JS a class object does not expose its
// prototype methods as static members: `class C { m(){} }` has
// `C.m === undefined` (the method lives on `C.prototype`). The
// earlier unconditional lookup leaked instance methods onto the
// class ref, so `C.m` returned a (mis-bound) function. This
// broke NestJS interceptor/guard/pipe resolution: its
// `getInterceptorInstance` duck-types `!!metatype.intercept` to
// decide "is this a class or an already-built instance"; a
// truthy `Class.intercept` made it treat the CLASS as the
// instance, so `intercept()` ran with a broken receiver and
// returned `{}`, which rxjs `innerFrom` then rejected. Real
// static methods are resolved below via
// `lookup_static_method_in_chain`.
if is_prototype_ref && class_id != 0 && class_has_own_method(class_id, name) {
let value = class_prototype_method_value_for_name(class_id, name);
return JSValue::from_bits(value.to_bits());
}
Expand Down
Loading