Skip to content

Commit 3e17cf7

Browse files
authored
Fix passing variables across compilations (#367)
1 parent bcbd09d commit 3e17cf7

File tree

5 files changed

+94
-22
lines changed

5 files changed

+94
-22
lines changed

lib/src/function-registry.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import {Value} from './value';
1818
* execute them.
1919
*/
2020
export class FunctionRegistry<sync extends 'sync' | 'async'> {
21+
/**
22+
* The globally unique identifier of the current compilation used for tracking
23+
* the ownership of CompilerFunction and CompilerMixin objects.
24+
*/
25+
public readonly compileContext = Symbol();
2126
private readonly functionsByName = new Map<string, CustomFunction<sync>>();
2227
private readonly functionsById = new Map<number, CustomFunction<sync>>();
2328
private readonly idsByFunction = new Map<CustomFunction<sync>, number>();

lib/src/protofier.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class Protofier {
4444
get accessedArgumentLists(): number[] {
4545
return this.argumentLists
4646
.filter(list => list.keywordsAccessed)
47-
.map(list => list.id);
47+
.map(list => list.id!);
4848
}
4949

5050
constructor(
@@ -85,15 +85,21 @@ export class Protofier {
8585
});
8686
result.value = {case: 'list', value: list};
8787
} else if (value instanceof SassArgumentList) {
88-
const list = create(proto.Value_ArgumentListSchema, {
89-
id: value.id,
90-
separator: this.protofySeparator(value.separator),
91-
contents: value.asList.map(element => this.protofy(element)).toArray(),
92-
});
93-
for (const [key, mapValue] of value.keywordsInternal) {
94-
list.keywords[key] = this.protofy(mapValue);
88+
if (value.compileContext === this.functions.compileContext) {
89+
const list = create(proto.Value_ArgumentListSchema, {id: value.id});
90+
result.value = {case: 'argumentList', value: list};
91+
} else {
92+
const list = create(proto.Value_ArgumentListSchema, {
93+
separator: this.protofySeparator(value.separator),
94+
contents: value.asList
95+
.map(element => this.protofy(element))
96+
.toArray(),
97+
});
98+
for (const [key, mapValue] of value.keywordsInternal) {
99+
list.keywords[key] = this.protofy(mapValue);
100+
}
101+
result.value = {case: 'argumentList', value: list};
95102
}
96-
result.value = {case: 'argumentList', value: list};
97103
} else if (value instanceof SassMap) {
98104
const map = create(proto.Value_MapSchema, {
99105
entries: value.contents.toArray().map(([key, value]) => ({
@@ -104,6 +110,11 @@ export class Protofier {
104110
result.value = {case: 'map', value: map};
105111
} else if (value instanceof SassFunction) {
106112
if (value.id !== undefined) {
113+
if (value.compileContext !== this.functions.compileContext) {
114+
throw utils.compilerError(
115+
`Value ${value} does not belong to this compilation`,
116+
);
117+
}
107118
const fn = create(proto.Value_CompilerFunctionSchema, value);
108119
result.value = {case: 'compilerFunction', value: fn};
109120
} else {
@@ -114,6 +125,11 @@ export class Protofier {
114125
result.value = {case: 'hostFunction', value: fn};
115126
}
116127
} else if (value instanceof SassMixin) {
128+
if (value.compileContext !== this.functions.compileContext) {
129+
throw utils.compilerError(
130+
`Value ${value} does not belong to this compilation`,
131+
);
132+
}
117133
const mixin = create(proto.Value_CompilerMixinSchema, value);
118134
result.value = {case: 'compilerMixin', value: mixin};
119135
} else if (value instanceof SassCalculation) {
@@ -349,6 +365,7 @@ export class Protofier {
349365
),
350366
separator,
351367
list.id,
368+
this.functions.compileContext,
352369
);
353370
this.argumentLists.push(result);
354371
return result;
@@ -369,15 +386,21 @@ export class Protofier {
369386
);
370387

371388
case 'compilerFunction':
372-
return new SassFunction(value.value.value.id);
389+
return new SassFunction(
390+
value.value.value.id,
391+
this.functions.compileContext,
392+
);
373393

374394
case 'hostFunction':
375395
throw utils.compilerError(
376396
'The compiler may not send Value.host_function.',
377397
);
378398

379399
case 'compilerMixin':
380-
return new SassMixin(value.value.value.id);
400+
return new SassMixin(
401+
value.value.value.id,
402+
this.functions.compileContext,
403+
);
381404

382405
case 'calculation':
383406
return this.deprotofyCalculation(value.value.value);

lib/src/value/argument-list.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,24 @@ export class SassArgumentList extends SassList {
1313
* compiler which argument lists have had their keywords accessed during a
1414
* function call.
1515
*
16-
* The special ID 0 indicates an argument list constructed in the host.
16+
* The special undefined indicates an argument list constructed in the host.
1717
*
1818
* This is marked as public so that the protofier can access it, but it's not
1919
* part of the package's public API and should not be accessed by user code.
2020
* It may be renamed or removed without warning in the future.
2121
*/
22-
readonly id: number;
22+
readonly id: number | undefined;
23+
24+
/**
25+
* If this argument list is constructed in the compiler, this is the unique
26+
* context that the host uses to determine which compilation this argument
27+
* list belongs to.
28+
*
29+
* This is marked as public so that the protofier can access it, but it's not
30+
* part of the package's public API and should not be accessed by user code.
31+
* It may be renamed or removed without warning in the future.
32+
*/
33+
readonly compileContext: symbol | undefined;
2334

2435
/**
2536
* The argument list's keywords. This isn't exposed directly so that we can
@@ -54,11 +65,13 @@ export class SassArgumentList extends SassList {
5465
keywords: Record<string, Value> | OrderedMap<string, Value>,
5566
separator?: ListSeparator,
5667
id?: number,
68+
compileContext?: symbol,
5769
) {
5870
super(contents, {separator});
5971
this.keywordsInternal = isOrderedMap(keywords)
6072
? keywords
6173
: OrderedMap(keywords);
62-
this.id = id ?? 0;
74+
this.id = id;
75+
this.compileContext = compileContext;
6376
}
6477
}

lib/src/value/function.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ export class SassFunction extends Value {
1919
*/
2020
readonly id: number | undefined;
2121

22+
/**
23+
* If this function is defined in the compiler, this is the unique context
24+
* that the host uses to determine which compilation this function belongs to.
25+
*
26+
* This is marked as public so that the protofier can access it, but it's not
27+
* part of the package's public API and should not be accessed by user code.
28+
* It may be renamed or removed without warning in the future.
29+
*/
30+
readonly compileContext: symbol | undefined;
31+
2232
/**
2333
* If this function is defined in the host, this is the signature that
2434
* describes how to pass arguments to it.
@@ -39,26 +49,32 @@ export class SassFunction extends Value {
3949
*/
4050
readonly callback: CustomFunction<'sync'> | undefined;
4151

42-
constructor(id: number);
52+
constructor(id: number, compileContext: symbol);
4353
constructor(signature: string, callback: CustomFunction<'sync'>);
4454
constructor(
4555
idOrSignature: number | string,
46-
callback?: CustomFunction<'sync'>,
56+
callbackOrCompileContext: CustomFunction<'sync'> | symbol,
4757
) {
4858
super();
4959

50-
if (typeof idOrSignature === 'number') {
60+
if (
61+
typeof idOrSignature === 'number' &&
62+
typeof callbackOrCompileContext === 'symbol'
63+
) {
5164
this.id = idOrSignature;
65+
this.compileContext = callbackOrCompileContext;
5266
} else {
53-
this.signature = idOrSignature;
54-
this.callback = callback!;
67+
this.signature = idOrSignature as string;
68+
this.callback = callbackOrCompileContext as CustomFunction<'sync'>;
5569
}
5670
}
5771

5872
equals(other: Value): boolean {
5973
return this.id === undefined
6074
? other === this
61-
: other instanceof SassFunction && other.id === this.id;
75+
: other instanceof SassFunction &&
76+
other.compileContext === this.compileContext &&
77+
other.id === this.id;
6278
}
6379

6480
hashCode(): number {

lib/src/value/mixin.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,28 @@ export class SassMixin extends Value {
1818
*/
1919
readonly id: number;
2020

21-
constructor(id: number) {
21+
/**
22+
* This is the unique context that the host uses to determine which
23+
* compilation this mixin belongs to.
24+
*
25+
* This is marked as public so that the protofier can access it, but it's not
26+
* part of the package's public API and should not be accessed by user code.
27+
* It may be renamed or removed without warning in the future.
28+
*/
29+
readonly compileContext: symbol;
30+
31+
constructor(id: number, compileContext: symbol) {
2232
super();
2333
this.id = id;
34+
this.compileContext = compileContext;
2435
}
2536

2637
equals(other: Value): boolean {
27-
return other instanceof SassMixin && other.id === this.id;
38+
return (
39+
other instanceof SassMixin &&
40+
other.compileContext === this.compileContext &&
41+
other.id === this.id
42+
);
2843
}
2944

3045
hashCode(): number {

0 commit comments

Comments
 (0)