using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Sim.Runner;
///
/// 编译前改写学生代码里的旧版线程 API,让教材项目 5 的 Suspend / Resume / Abort 能在新版 .NET 上运行:
///
/// new Thread(GetData) → new Thread(LegacyThreads.Wrap(new ThreadStart(GetData)))
/// thd.Suspend() / Resume() / Abort() → LegacyThreads.Suspend(thd) …
/// thd.ThreadState → LegacyThreads.GetThreadState(thd)
/// Thread.Sleep(2000) → LegacyThreads.Sleep(2000)
///
/// 只改写语义上确实是 System.Threading.Thread 成员的调用;改写不换行,报错和异常堆栈里的行号不变。
/// 学生在编辑器里看到的仍是原来的代码。
///
internal static class LegacyThreadRewriter
{
private const string Target = "global::Sim.Host.LegacyThreads";
/// 改写整个编译;没有需要改写的地方时原样返回
public static CSharpCompilation Rewrite(CSharpCompilation compilation)
{
var result = compilation;
foreach (var tree in compilation.SyntaxTrees)
{
// 用到线程的文件一定会出现 Thread 这个词,其他文件不用做语义分析
if (!tree.GetText().ToString().Contains("Thread")) continue;
var rewriter = new Rewriter(compilation.GetSemanticModel(tree));
var root = rewriter.Visit(tree.GetRoot());
if (!rewriter.Changed) continue;
var text = SourceText.From(root.ToFullString(), Encoding.UTF8);
result = result.ReplaceSyntaxTree(tree, CSharpSyntaxTree.ParseText(text, (CSharpParseOptions)tree.Options, tree.FilePath));
}
return result;
}
/// 平台支持的过时线程 API 不再提示"已过时"(边写边检查时用)
public static bool IsSupportedObsoleteWarning(Diagnostic diagnostic)
{
if (diagnostic.Id is not ("CS0618" or "SYSLIB0006")) return false;
// 警告标在整个调用上,如 thd.Suspend()
var source = diagnostic.Location.SourceTree?.GetText().ToString(diagnostic.Location.SourceSpan) ?? "";
return Regex.IsMatch(source, @"\b(Suspend|Resume|Abort)\b");
}
private static bool IsThread(ITypeSymbol? type) =>
type is { Name: "Thread", ContainingNamespace: { Name: "Threading", ContainingNamespace: { Name: "System", ContainingNamespace.IsGlobalNamespace: true } } };
private sealed class Rewriter(SemanticModel model) : CSharpSyntaxRewriter
{
public bool Changed { get; private set; }
public override SyntaxNode? VisitInvocationExpression(InvocationExpressionSyntax node)
{
var method = model.GetSymbolInfo(node).Symbol as IMethodSymbol;
var visited = (InvocationExpressionSyntax)base.VisitInvocationExpression(node)!;
if (method == null || !IsThread(method.ContainingType)) return visited;
switch (method.Name)
{
case "Suspend" or "Resume" or "Abort" when !method.IsStatic && visited.Expression is MemberAccessExpressionSyntax access:
// thd.Abort(x) → LegacyThreads.Abort(thd, x)
var arguments = visited.ArgumentList.Arguments.Insert(0, SyntaxFactory.Argument(access.Expression.WithoutTrivia()));
Changed = true;
return SyntaxFactory.InvocationExpression(Member(method.Name), SyntaxFactory.ArgumentList(arguments))
.WithTriviaFrom(visited);
case "Sleep" when method.IsStatic:
Changed = true;
return visited.WithExpression(Member("Sleep").WithTriviaFrom(visited.Expression));
default:
return visited;
}
}
public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
{
var symbol = model.GetSymbolInfo(node).Symbol;
var visited = (MemberAccessExpressionSyntax)base.VisitMemberAccessExpression(node)!;
if (symbol is not IPropertySymbol { Name: "ThreadState", IsStatic: false } property || !IsThread(property.ContainingType)) return visited;
Changed = true;
return SyntaxFactory.InvocationExpression(
Member("GetThreadState"),
SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(visited.Expression.WithoutTrivia()))))
.WithTriviaFrom(visited);
}
public override SyntaxNode? VisitObjectCreationExpression(ObjectCreationExpressionSyntax node) =>
WrapThreadStart(node, base.VisitObjectCreationExpression(node));
public override SyntaxNode? VisitImplicitObjectCreationExpression(ImplicitObjectCreationExpressionSyntax node) =>
WrapThreadStart(node, base.VisitImplicitObjectCreationExpression(node));
/// new Thread(start) 的第一个参数包一层 LegacyThreads.Wrap,线程被 Abort 时安静地结束
private SyntaxNode? WrapThreadStart(BaseObjectCreationExpressionSyntax original, SyntaxNode? visitedNode)
{
if (visitedNode is not BaseObjectCreationExpressionSyntax visited
|| model.GetSymbolInfo(original).Symbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } constructor
|| !IsThread(constructor.ContainingType)
|| constructor.Parameters.Length == 0
|| visited.ArgumentList is not { Arguments.Count: > 0 } argumentList)
{
return visitedNode;
}
var first = argumentList.Arguments[0];
if (first.Expression.IsKind(SyntaxKind.NullLiteralExpression)) return visited;
var delegateType = constructor.Parameters[0].Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
// new ThreadStart(方法名或 lambda):明确委托类型,Wrap 的重载就不会有歧义
var wrapped = SyntaxFactory.ParseExpression($"{Target}.Wrap(new {delegateType}({first.Expression.WithoutTrivia().ToFullString()}))");
Changed = true;
return visited.WithArgumentList(argumentList.WithArguments(
argumentList.Arguments.Replace(first, first.WithExpression(wrapped.WithTriviaFrom(first.Expression)))));
}
private static ExpressionSyntax Member(string name) => SyntaxFactory.ParseExpression($"{Target}.{name}");
}
}