Skip to content

Commit 2672a83

Browse files
committed
Support User Defines and Macro References AB Experiment
- Depends on #12979 to merge first. - copilotcppMacroReferenceFilter: regex string to filter macro reference for telemetry. - Telemetry related to user defines and macro references. - Added new trait compilerUserDefines to note the relevant user defines to the editing file.
1 parent 92e251b commit 2672a83

File tree

5 files changed

+123
-10
lines changed

5 files changed

+123
-10
lines changed

Extension/src/LanguageServer/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ export interface ChatContextResult {
543543

544544
export interface FileContextResult {
545545
compilerArguments: string[];
546+
compilerUserDefines: string[];
547+
macroReferences: string[];
546548
}
547549

548550
export interface ProjectContextResult {

Extension/src/LanguageServer/copilotProviders.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export async function registerRelatedFilesProvider(): Promise<void> {
7979
const compilerArgumentsValue = updatedArguments.join(", ");
8080
traits.push({ name: "compilerArguments", value: compilerArgumentsValue, includeInPrompt: true, promptTextOverride: `The compiler arguments include: ${compilerArgumentsValue}.` });
8181
}
82+
if (cppContext.compilerUserDefinesRelevant.length > 0) {
83+
const compilerUserDefinesValue = cppContext.compilerUserDefinesRelevant.join(", ");
84+
traits.push({ name: "compilerUserDefines", value: compilerUserDefinesValue, includeInPrompt: true, promptTextOverride: `These compiler command line user defines may be relevent: ${compilerUserDefinesValue}.` });
85+
}
8286
if (directAsks) {
8387
traits.push({ name: "directAsks", value: directAsks, includeInPrompt: true, promptTextOverride: directAsks });
8488
}

Extension/src/LanguageServer/lmTool.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export interface ProjectContext {
6666
targetPlatform: string;
6767
targetArchitecture: string;
6868
compilerArguments: string[];
69+
compilerUserDefinesRelevant: string[];
6970
}
7071

7172
// Set these values for local testing purpose without involving control tower.
@@ -93,6 +94,15 @@ function filterComplierArguments(compiler: string, compilerArguments: string[],
9394
return compilerArguments.filter(arg => defaultFilter?.test(arg) || additionalFilter?.test(arg));
9495
}
9596

97+
// We can set up copilotcppMacroReferenceFilter feature flag to filter macro references to learn about
98+
// macro usage distribution, i.e., compiler or platform specific macros, or even the presence of certain macros.
99+
const defaultMacroReferenceFilter: RegExp | undefined = undefined;
100+
function filterMacroReferences(macroReferences: string[], context: { flags: Record<string, unknown> }): string[] {
101+
const filter: RegExp | undefined = context.flags.copilotcppMacroReferenceFilter ? new RegExp(context.flags.copilotcppMacroReferenceFilter as string) : undefined;
102+
103+
return macroReferences.filter(macro => defaultMacroReferenceFilter?.test(macro) || filter?.test(macro));
104+
}
105+
96106
export async function getProjectContext(context: { flags: Record<string, unknown> }, token: vscode.CancellationToken): Promise<ProjectContext | undefined> {
97107
const telemetryProperties: Record<string, string> = {};
98108
try {
@@ -110,7 +120,8 @@ export async function getProjectContext(context: { flags: Record<string, unknown
110120
compiler: projectContext.result.compiler,
111121
targetPlatform: projectContext.result.targetPlatform,
112122
targetArchitecture: projectContext.result.targetArchitecture,
113-
compilerArguments: []
123+
compilerArguments: [],
124+
compilerUserDefinesRelevant: []
114125
};
115126

116127
if (projectContext.result.language) {
@@ -129,14 +140,32 @@ export async function getProjectContext(context: { flags: Record<string, unknown
129140
telemetryProperties["targetArchitecture"] = projectContext.result.targetArchitecture;
130141
}
131142
telemetryProperties["compilerArgumentCount"] = projectContext.result.fileContext.compilerArguments.length.toString();
132-
// Telemtry to learn about the argument distribution. The filtered arguments are expected to be non-PII.
143+
// Telemtry to learn about the argument and macro distribution. The filtered arguments and macro references
144+
// are expected to be non-PII.
133145
if (projectContext.result.fileContext.compilerArguments.length) {
134146
const filteredCompilerArguments = filterComplierArguments(projectContext.result.compiler, projectContext.result.fileContext.compilerArguments, context);
135147
if (filteredCompilerArguments.length > 0) {
136148
telemetryProperties["filteredCompilerArguments"] = filteredCompilerArguments.join(', ');
137149
result.compilerArguments = filteredCompilerArguments;
138150
}
139151
}
152+
telemetryProperties["compilerUserDefinesCount"] = projectContext.result.fileContext.compilerUserDefines.length.toString();
153+
if (projectContext.result.fileContext.compilerUserDefines.length > 0) {
154+
const userDefinesWithoutValue = projectContext.result.fileContext.compilerUserDefines.map(value => value.split('=')[0]);
155+
const userDefinesRelatedToThisFile = userDefinesWithoutValue.filter(value => projectContext.result?.fileContext.macroReferences.includes(value));
156+
if (userDefinesRelatedToThisFile.length > 0) {
157+
// Don't care the actual name of the user define, just the count that's relevant.
158+
telemetryProperties["compilerUserDefinesRelevantCount"] = userDefinesRelatedToThisFile.length.toString();
159+
result.compilerUserDefinesRelevant = userDefinesRelatedToThisFile;
160+
}
161+
}
162+
telemetryProperties["macroReferenceCount"] = projectContext.result.fileContext.macroReferences.length.toString();
163+
if (projectContext.result.fileContext.macroReferences.length > 0) {
164+
const filteredMacroReferences = filterMacroReferences(projectContext.result.fileContext.macroReferences, context);
165+
if (filteredMacroReferences.length > 0) {
166+
telemetryProperties["filteredMacroReferences"] = filteredMacroReferences.join(', ');
167+
}
168+
}
140169

141170
return result;
142171
}

Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ describe('copilotProviders Tests', () => {
138138
compiler: 'MSVC',
139139
targetPlatform: 'Windows',
140140
targetArchitecture: 'x64',
141-
compilerArguments: []
141+
compilerArguments: [],
142+
compilerUserDefinesRelevant: []
142143
};
143144

144145
it('should not provide project context traits when copilotcppTraits flag is false.', async () => {
@@ -245,7 +246,8 @@ describe('copilotProviders Tests', () => {
245246
compiler: 'MSVC',
246247
targetPlatform: 'Windows',
247248
targetArchitecture: 'x64',
248-
compilerArguments: ['/std:c++17', '/GR-', '/EHs-c-', '/await']
249+
compilerArguments: ['/std:c++17', '/GR-', '/EHs-c-', '/await'],
250+
compilerUserDefinesRelevant: ['MY_FOO', 'MY_BAR']
249251
};
250252

251253
it('should provide compiler command line traits if available.', async () => {
@@ -266,6 +268,10 @@ describe('copilotProviders Tests', () => {
266268
ok(result.traits.find((trait) => trait.name === 'compilerArguments')?.value === '/std:c++17, /GR-, /EHs-c-, /await', 'result.traits should have a compiler arguments trait with value "/std:c++17, /GR-, /EHs-c-, /await"');
267269
ok(result.traits.find((trait) => trait.name === 'compilerArguments')?.includeInPrompt, 'result.traits should have a compiler arguments trait with includeInPrompt true');
268270
ok(result.traits.find((trait) => trait.name === 'compilerArguments')?.promptTextOverride === 'The compiler arguments include: /std:c++17, /GR-, /EHs-c-, /await.', 'result.traits should have a compiler arguments trait with promptTextOverride');
271+
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines'), 'result.traits should have a compiler user defines trait');
272+
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines')?.value === 'MY_FOO, MY_BAR', 'result.traits should have a compiler user defines trait with value "MY_FOO, MY_BAR"');
273+
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines')?.includeInPrompt, 'result.traits should have a compiler user defines trait with includeInPrompt true');
274+
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines')?.promptTextOverride === 'These compiler command line user defines may be relevent: MY_FOO, MY_BAR.', 'result.traits should have a compiler args trait with promptTextOverride');
269275
ok(!result.traits.find((trait) => trait.name === 'directAsks'), 'result.traits should not have a direct asks trait');
270276
});
271277

Extension/test/scenarios/SingleRootProject/tests/lmTool.test.ts

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,21 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
179179
expectedCompiler,
180180
context,
181181
compilerArguments: compilerArguments,
182-
expectedCompilerArguments
182+
expectedCompilerArguments,
183+
compilerUserDefines,
184+
expectedCompilerUserDefines,
185+
macroReferences,
186+
expectedMacroReferences
183187
}: {
184188
compiler: string;
185189
expectedCompiler: string;
186190
context: { flags: Record<string, unknown> };
187191
compilerArguments: string[];
188192
expectedCompilerArguments: string[];
193+
compilerUserDefines: string[];
194+
expectedCompilerUserDefines: string[];
195+
macroReferences: string[];
196+
expectedMacroReferences: string[];
189197
}) => {
190198
arrangeProjectContextFromCppTools({
191199
projectContextFromCppTools: {
@@ -195,7 +203,9 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
195203
targetPlatform: 'windows',
196204
targetArchitecture: 'x64',
197205
fileContext: {
198-
compilerArguments: compilerArguments
206+
compilerArguments: compilerArguments,
207+
compilerUserDefines: compilerUserDefines,
208+
macroReferences: macroReferences
199209
}
200210
}
201211
});
@@ -228,6 +238,22 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
228238
"filteredCompilerArguments": expectedCompilerArguments.join(', ')
229239
})));
230240
}
241+
ok(languageModelToolTelemetryStub.calledWithMatch('Completions/tool', sinon.match({
242+
"compilerUserDefinesCount": compilerUserDefines.length.toString()
243+
})));
244+
if (expectedCompilerUserDefines.length > 0) {
245+
ok(languageModelToolTelemetryStub.calledWithMatch('Completions/tool', sinon.match({
246+
"compilerUserDefinesRelevantCount": expectedCompilerUserDefines.length.toString()
247+
})));
248+
}
249+
ok(languageModelToolTelemetryStub.calledWithMatch('Completions/tool', sinon.match({
250+
'macroReferenceCount': macroReferences.length.toString()
251+
})));
252+
if (expectedMacroReferences.length > 0) {
253+
ok(languageModelToolTelemetryStub.calledWithMatch('Completions/tool', sinon.match({
254+
'filteredMacroReferences': expectedMacroReferences.join(', ')
255+
})));
256+
}
231257
ok(languageModelToolTelemetryStub.calledWithMatch('Completions/tool', sinon.match({
232258
'time': sinon.match.string
233259
})));
@@ -238,6 +264,7 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
238264
ok(result.targetPlatform === 'Windows');
239265
ok(result.targetArchitecture === 'x64');
240266
ok(JSON.stringify(result.compilerArguments) === JSON.stringify(expectedCompilerArguments));
267+
ok(JSON.stringify(result.compilerUserDefinesRelevant) === JSON.stringify(expectedCompilerUserDefines));
241268
};
242269

243270
it('should log telemetry and provide cpp context properly when experimental flags are not defined.', async () => {
@@ -246,7 +273,11 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
246273
expectedCompiler: 'GCC',
247274
context: { flags: {} },
248275
compilerArguments: ['-Wall', '-Werror', '-std=c++20'],
249-
expectedCompilerArguments: []
276+
expectedCompilerArguments: [],
277+
compilerUserDefines: [],
278+
expectedCompilerUserDefines: [],
279+
macroReferences: [],
280+
expectedMacroReferences: []
250281
});
251282
});
252283

@@ -256,7 +287,39 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
256287
expectedCompiler: 'GCC',
257288
context: { flags: { copilotcppGccCompilerArgumentFilter: '^-(fno\-exceptions|fno\-rtti)$' } },
258289
compilerArguments: ['-Wall', '-Werror', '-std=c++20', '-fno-exceptions', '-fno-rtti', '-pthread', '-O3', '-funroll-loops'],
259-
expectedCompilerArguments: ['-fno-exceptions', '-fno-rtti']
290+
expectedCompilerArguments: ['-fno-exceptions', '-fno-rtti'],
291+
compilerUserDefines: [],
292+
expectedCompilerUserDefines: [],
293+
macroReferences: [],
294+
expectedMacroReferences: []
295+
});
296+
});
297+
298+
it('should honor content-exclusion and provide user defines based on macro references.', async () => {
299+
await testGetProjectContext({
300+
compiler: 'gcc',
301+
expectedCompiler: 'GCC',
302+
context: { flags: {} },
303+
compilerArguments: [],
304+
expectedCompilerArguments: [],
305+
compilerUserDefines: ['FOO', 'BAR', 'XYZ'],
306+
expectedCompilerUserDefines: ['FOO', 'XYZ'],
307+
macroReferences: ['FOO', 'XYZ', 'BAZ'],
308+
expectedMacroReferences: []
309+
});
310+
});
311+
312+
it('should log macro reference telemetry based on copilotcppMacroReferenceFilter.', async () => {
313+
await testGetProjectContext({
314+
compiler: 'gcc',
315+
expectedCompiler: 'GCC',
316+
context: { flags: { copilotcppMacroReferenceFilter: '^(FOO|XYZ)$' } },
317+
compilerArguments: [],
318+
expectedCompilerArguments: [],
319+
compilerUserDefines: [],
320+
expectedCompilerUserDefines: [],
321+
macroReferences: ['FOO', 'XYZ', 'BAZ'],
322+
expectedMacroReferences: ['FOO', 'XYZ']
260323
});
261324
});
262325

@@ -272,7 +335,11 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
272335
}
273336
},
274337
compilerArguments: ['-fno-exceptions', '-fno-rtti'],
275-
expectedCompilerArguments: []
338+
expectedCompilerArguments: [],
339+
compilerUserDefines: [],
340+
expectedCompilerUserDefines: [],
341+
macroReferences: [],
342+
expectedMacroReferences: []
276343
});
277344
});
278345

@@ -285,7 +352,9 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
285352
targetPlatform: 'arduino',
286353
targetArchitecture: 'bar',
287354
fileContext: {
288-
compilerArguments: []
355+
compilerArguments: [],
356+
compilerUserDefines: [],
357+
macroReferences: []
289358
}
290359
}
291360
});
@@ -295,6 +364,8 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
295364
ok(languageModelToolTelemetryStub.calledOnce, 'logLanguageModelToolEvent should be called once');
296365
ok(languageModelToolTelemetryStub.calledWithMatch('Completions/tool', sinon.match({
297366
"compilerArgumentCount": '0',
367+
"compilerUserDefinesCount": '0',
368+
"macroReferenceCount": '0',
298369
"targetArchitecture": 'bar'
299370
})));
300371
ok(result, 'result should not be undefined');
@@ -304,5 +375,6 @@ describe('CppConfigurationLanguageModelTool Tests', () => {
304375
ok(result.targetPlatform === '');
305376
ok(result.targetArchitecture === 'bar');
306377
ok(result.compilerArguments.length === 0);
378+
ok(result.compilerUserDefinesRelevant.length === 0);
307379
});
308380
});

0 commit comments

Comments
 (0)