diff --git a/lib/src/function-registry.ts b/lib/src/function-registry.ts index 8d5fe45c..e9e8d1fc 100644 --- a/lib/src/function-registry.ts +++ b/lib/src/function-registry.ts @@ -18,6 +18,7 @@ import {Value} from './value'; * execute them. */ export class FunctionRegistry { + public readonly compileContext = Symbol(); private readonly functionsByName = new Map>(); private readonly functionsById = new Map>(); private readonly idsByFunction = new Map, number>(); diff --git a/lib/src/protofier.ts b/lib/src/protofier.ts index 95be0868..67f2ccca 100644 --- a/lib/src/protofier.ts +++ b/lib/src/protofier.ts @@ -44,7 +44,7 @@ export class Protofier { get accessedArgumentLists(): number[] { return this.argumentLists .filter(list => list.keywordsAccessed) - .map(list => list.id); + .map(list => list.id as number); } constructor( @@ -85,15 +85,23 @@ export class Protofier { }); result.value = {case: 'list', value: list}; } else if (value instanceof SassArgumentList) { - const list = create(proto.Value_ArgumentListSchema, { - id: value.id, - separator: this.protofySeparator(value.separator), - contents: value.asList.map(element => this.protofy(element)).toArray(), - }); - for (const [key, mapValue] of value.keywordsInternal) { - list.keywords[key] = this.protofy(mapValue); + if (value.compileContext === this.functions.compileContext) { + const list = create(proto.Value_ArgumentListSchema, { + id: value.id, + }); + result.value = {case: 'argumentList', value: list}; + } else { + const list = create(proto.Value_ArgumentListSchema, { + separator: this.protofySeparator(value.separator), + contents: value.asList + .map(element => this.protofy(element)) + .toArray(), + }); + for (const [key, mapValue] of value.keywordsInternal) { + list.keywords[key] = this.protofy(mapValue); + } + result.value = {case: 'argumentList', value: list}; } - result.value = {case: 'argumentList', value: list}; } else if (value instanceof SassMap) { const map = create(proto.Value_MapSchema, { entries: value.contents.toArray().map(([key, value]) => ({ @@ -104,6 +112,11 @@ export class Protofier { result.value = {case: 'map', value: map}; } else if (value instanceof SassFunction) { if (value.id !== undefined) { + if (value.compileContext !== this.functions.compileContext) { + throw utils.compilerError( + `Value ${value} does not belong to this compilation`, + ); + } const fn = create(proto.Value_CompilerFunctionSchema, value); result.value = {case: 'compilerFunction', value: fn}; } else { @@ -114,6 +127,11 @@ export class Protofier { result.value = {case: 'hostFunction', value: fn}; } } else if (value instanceof SassMixin) { + if (value.compileContext !== this.functions.compileContext) { + throw utils.compilerError( + `Value ${value} does not belong to this compilation`, + ); + } const mixin = create(proto.Value_CompilerMixinSchema, value); result.value = {case: 'compilerMixin', value: mixin}; } else if (value instanceof SassCalculation) { @@ -349,6 +367,7 @@ export class Protofier { ), separator, list.id, + this.functions.compileContext, ); this.argumentLists.push(result); return result; @@ -369,7 +388,10 @@ export class Protofier { ); case 'compilerFunction': - return new SassFunction(value.value.value.id); + return new SassFunction( + value.value.value.id, + this.functions.compileContext, + ); case 'hostFunction': throw utils.compilerError( @@ -377,7 +399,10 @@ export class Protofier { ); case 'compilerMixin': - return new SassMixin(value.value.value.id); + return new SassMixin( + value.value.value.id, + this.functions.compileContext, + ); case 'calculation': return this.deprotofyCalculation(value.value.value); diff --git a/lib/src/value/argument-list.ts b/lib/src/value/argument-list.ts index ea1ae188..14d2aa9e 100644 --- a/lib/src/value/argument-list.ts +++ b/lib/src/value/argument-list.ts @@ -19,7 +19,18 @@ export class SassArgumentList extends SassList { * part of the package's public API and should not be accessed by user code. * It may be renamed or removed without warning in the future. */ - readonly id: number; + readonly id: number | undefined; + + /** + * If this argument list is constructed in the compiler, this is the unique + * context that the host uses to determine which compilation this argument + * list belongs to. + * + * This is marked as public so that the protofier can access it, but it's not + * part of the package's public API and should not be accessed by user code. + * It may be renamed or removed without warning in the future. + */ + readonly compileContext: symbol | undefined; /** * The argument list's keywords. This isn't exposed directly so that we can @@ -54,11 +65,13 @@ export class SassArgumentList extends SassList { keywords: Record | OrderedMap, separator?: ListSeparator, id?: number, + compileContext?: symbol, ) { super(contents, {separator}); this.keywordsInternal = isOrderedMap(keywords) ? keywords : OrderedMap(keywords); - this.id = id ?? 0; + this.id = id; + this.compileContext = compileContext; } } diff --git a/lib/src/value/function.ts b/lib/src/value/function.ts index 68f52307..f2c1d796 100644 --- a/lib/src/value/function.ts +++ b/lib/src/value/function.ts @@ -19,6 +19,16 @@ export class SassFunction extends Value { */ readonly id: number | undefined; + /** + * If this function is defined in the compiler, this is the unique context + * that the host uses to determine which compilation this function belongs to. + * + * This is marked as public so that the protofier can access it, but it's not + * part of the package's public API and should not be accessed by user code. + * It may be renamed or removed without warning in the future. + */ + readonly compileContext: symbol | undefined; + /** * If this function is defined in the host, this is the signature that * describes how to pass arguments to it. @@ -39,26 +49,32 @@ export class SassFunction extends Value { */ readonly callback: CustomFunction<'sync'> | undefined; - constructor(id: number); + constructor(id: number, compileContext: symbol); constructor(signature: string, callback: CustomFunction<'sync'>); constructor( idOrSignature: number | string, - callback?: CustomFunction<'sync'>, + callbackOrCompileContext: CustomFunction<'sync'> | symbol, ) { super(); - if (typeof idOrSignature === 'number') { + if ( + typeof idOrSignature === 'number' && + typeof callbackOrCompileContext === 'symbol' + ) { this.id = idOrSignature; + this.compileContext = callbackOrCompileContext; } else { - this.signature = idOrSignature; - this.callback = callback!; + this.signature = idOrSignature as string; + this.callback = callbackOrCompileContext as CustomFunction<'sync'>; } } equals(other: Value): boolean { return this.id === undefined ? other === this - : other instanceof SassFunction && other.id === this.id; + : other instanceof SassFunction && + other.compileContext === this.compileContext && + other.id === this.id; } hashCode(): number { diff --git a/lib/src/value/mixin.ts b/lib/src/value/mixin.ts index 1d8ecd86..ce17e9f8 100644 --- a/lib/src/value/mixin.ts +++ b/lib/src/value/mixin.ts @@ -18,13 +18,28 @@ export class SassMixin extends Value { */ readonly id: number; - constructor(id: number) { + /** + * This is the unique context that the host uses to determine which + * compilation this mixin belongs to. + * + * This is marked as public so that the protofier can access it, but it's not + * part of the package's public API and should not be accessed by user code. + * It may be renamed or removed without warning in the future. + */ + readonly compileContext: symbol; + + constructor(id: number, compileContext: symbol) { super(); this.id = id; + this.compileContext = compileContext; } equals(other: Value): boolean { - return other instanceof SassMixin && other.id === this.id; + return ( + other instanceof SassMixin && + other.compileContext === this.compileContext && + other.id === this.id + ); } hashCode(): number {