in resharper/resharper-unity/src/Unity/CSharp/Psi/UnityCSharpLanguageLevelProvider.cs [24:246]
internal sealed class UnityCSharpLanguageLevelProvider([NotNull] CSharpLanguageLevelProjectProperty projectProperty)
: CSharpLanguageLevelProvider(projectProperty)
{
private static readonly Key<CachedProjectLanguageLevel> ourGeneratedProjectLanguageLevelKey = new(nameof(ourGeneratedProjectLanguageLevelKey));
public override ILanguageVersionModifier<CSharpLanguageVersion> LanguageVersionModifier => null; // disable ability to modify language version
public override bool IsApplicable(IPsiModule psiModule)
{
return psiModule is IProjectPsiModule projectPsiModule
#pragma warning disable CS0618 // Type or member is obsolete
&& projectPsiModule.PsiLanguage.Is<CSharpLanguage>()
#pragma warning restore CS0618 // Type or member is obsolete
&& psiModule.ContainingProjectModule is IProject project
&& project.IsUnityGeneratedProject();
}
public override CSharpLanguageLevel GetLatestAvailableLanguageLevel(IPsiModule psiModule )=> GetCachedProjectLanguageLevel(psiModule).GetValue().previewAvailableLanguageValue ?? base.GetLatestAvailableLanguageLevel(psiModule);
public override CSharpLanguageLevel GetLanguageLevel(IPsiModule psiModule) => GetCachedProjectLanguageLevel(psiModule).GetValue().languageLevel ?? base.GetLanguageLevel(psiModule);
private static CachedProjectLanguageLevel GetCachedProjectLanguageLevel(IPsiModule psiModule)
{
var project = (IProject)psiModule.ContainingProjectModule.NotNull();
var cachedProjectLanguageLevel = project.GetOrCreateDataNoLock(
ourGeneratedProjectLanguageLevelKey,
project,
static project => new CachedProjectLanguageLevel(project));
return cachedProjectLanguageLevel;
}
public override bool IsAvailable(CSharpLanguageLevel languageLevel, IPsiModule psiModule)
{
var latestAvailableLanguageLevel = GetLatestAvailableLanguageLevel(psiModule);
return languageLevel <= latestAvailableLanguageLevel;
}
public override bool IsAvailable(CSharpLanguageVersion languageVersion, IPsiModule psiModule)
{
var languageLevel = ConvertToLanguageLevel(languageVersion, psiModule);
var latestAvailableLanguageLevel = GetLatestAvailableLanguageLevel(psiModule);
return languageLevel <= latestAvailableLanguageLevel;
}
public override CSharpLanguageLevel ConvertToLanguageLevel(CSharpLanguageVersion languageVersion, IPsiModule psiModule)
{
if (languageVersion is CSharpLanguageVersion.Default or CSharpLanguageVersion.Latest or CSharpLanguageVersion.LatestMajor)
return GetCachedProjectLanguageLevel(psiModule).GetValue().latestAvailableLanguageValue ?? base.ConvertToLanguageLevel(languageVersion, psiModule);
if (languageVersion is CSharpLanguageVersion.Preview)
return GetCachedProjectLanguageLevel(psiModule).GetValue().previewAvailableLanguageValue ?? base.ConvertToLanguageLevel(languageVersion, psiModule);
return base.ConvertToLanguageLevel(languageVersion, psiModule);
}
private sealed class CachedProjectLanguageLevel([NotNull] IProject project)
: CachedProjectItemAnyChange<IProject, (CSharpLanguageLevel? languageLevel, CSharpLanguageLevel? latestAvailableLanguageValue, CSharpLanguageLevel? previewAvailableLanguageValue)>(project.GetSolution().Timestamps, project, Evaluate)
{
private static (CSharpLanguageLevel? languageLevel, CSharpLanguageLevel? latestAvailableLanguageValue, CSharpLanguageLevel? previewAvailableLanguageValue) Evaluate(IProject project)
{
#region Explanation
// In Unity 2019.2 - Unity 6
// LangVersion is specified, can be modified by csc.rsp
// There is a certain amount of C# lang features which don't respect LangVersion, but depend on Roslyn capabilities,
// thus we need latestAvailableLanguageValue.
// see: RIDER-119992 Incorrect not initialized variable errors
// Older Unity versions:
// Make sure we don't suggest code changes that won't compile in Unity due to mismatched C# language levels
// (e.g. C#6 "elvis" operator)
//
// * Unity prior to 5.5 uses an old mono compiler that only supports C# 4
// * Unity 5.5 and later adds C# 6 support as an option. This is enabled by setting
// the API compatibility level to NET_4_6
// * The CSharp60Support plugin replaces the compiler with either C# 6 or C# 7.0 or 8.0
// It can be recognised by a folder called `CSharp60Support` or `CSharp70Support` or `CSharp80Support`
// in the root of the project
// (https://bitbucket.org/alexzzzz/unity-c-5.0-and-6.0-integration)
// * Note that since Unity 2017.2 till 2018.3, we've been special-cased in the Unity csproj generation
// and we've been getting v4.5 for old runtime and default values (4.7.1) for new. So where
// it says 3.5 below, that depends on the version of Unity. Older versions will give us 3.5,
// newer versions 4.5.
//
// Scenarios:
// * No VSTU installed (including Unity 5.5)
// .csproj has NO `LangVersion`. `TargetFrameworkVersion` will be `v3.5`
// * Early versions of VSTU
// .csproj has NO `LangVersion`. `TargetFrameworkVersion` will be `v3.5`
// * Later versions of VSTU
// `LangVersion` is correctly set to "4". `TargetFrameworkVersion` will be `v3.5`
// OR `LangVersion` is set to "6" or "latest".
// * VSTU for 5.5
// `LangVersion` is set to "default". `TargetFrameworkVersion` will be `v3.5` or `v4.6`
// Note that "default" for VS"15" or Rider will be C# 7.0!
// * Unity3dRider is installed
// Uses Unity's own generation and adds correct `LangVersion`
// `TargetFrameworkVersion` will be correct for the selected runtime
// * CSharp60Support is installed
// .csproj has NO `LangVersion`
// `TargetFrameworkVersion` is NOT accurate (support for C# 6 is not dependent on/trigger by .net 4.6)
// Look for `CSharp60Support` or `CSharp70Support` folders
// * Unity 2018.x+. LangVersion is `latest`. MSBuild 16 treats `newest` as C# 8. Re# and Rider start suggesting it.
//
// Actions:
// * If `LangVersion` is missing or "default"
// then override based on `TargetFrameworkVersion` or presence of `CSharp60Support`/`CSharp70Support`
// else do nothing
// * If `LangVersion` is "latest"
// then override based on `CSharp80Support` presence or LangVersion matching Roslyn bundled in Unity
//
// Notes:
// * Unity and VSTU have two separate .csproj routines. VSTU adds extra references,
// the VSTU project flavour GUID and imports UnityVs.targets, which disables the
// `GenerateTargetFrameworkMonikerAttribute` target
// * CSharp60Support post-processes the .csproj file directly if VSTU is not installed.
// If it is installed, it registers a delegate with `ProjectFilesGenerator.ProjectFileGeneration`
// and removes it before it's written to disk
// * `LangVersion` can be conditionally specified, which makes checking for "default" awkward
// * If Unity3dRider + CSharp60Support are both installed, last write wins
// Order of post-processing is non-deterministic, so Rider's LangVersion might be removed
#endregion
var unityProjectFileCacheProvider = project.GetComponent<UnityProjectFileCacheProvider>();
var appPath = unityProjectFileCacheProvider.GetAppPath(project);
var contentPath = UnityInstallationFinder.GetApplicationContentsPath(appPath);
if (!contentPath.IsNullOrEmpty())
{
var roslynDir = contentPath.Combine("Tools").Combine("Roslyn"); // older location
if (!roslynDir.ExistsDirectory)
roslynDir = contentPath.Combine("DotNetSdkRoslyn"); // Unity 6 location and maybe others
if (roslynDir.ExistsDirectory)
{
var languageLevelProjectProperty = project.GetComponent<CSharpLanguageLevelProjectProperty>();
var langVersion = project.ProjectProperties.ActiveConfigurations.Configurations
.OfType<CSharpProjectConfiguration>()
.Select(configuration => configuration.LanguageVersion)
.FirstOrDefault(CSharpLanguageVersion.Default);
var languageLevel = languageLevelProjectProperty.ConvertToLanguageLevel(langVersion, roslynDir);
var roslynVersion = RoslynUtil.GetRoslynVersion(roslynDir);
if (roslynVersion != null && roslynVersion.Major == 4 && roslynVersion.Minor == 3 && roslynVersion.Revision == 0 && roslynVersion.Build == 0)
{
// Approximately Unity 2022.3 till Unity 6
// RIDER-122524 Rider shows CS0618 for required members in referenced assemblies within Unity projects
return (languageLevel, CSharpLanguageLevel.CSharp110, CSharpLanguageLevel.CSharp110);
}
return (languageLevel, languageLevelProjectProperty.ConvertToLanguageLevel(CSharpLanguageVersion.Latest, roslynDir),
languageLevelProjectProperty.ConvertToLanguageLevel(CSharpLanguageVersion.Preview, roslynDir));
}
}
if (project.IsDotNetCoreProject()) // avoid affecting Unity 7+, see RIDER-124621
return (null, null, null);
return DetermineCSharpLanguageLevelOldUnity(project, unityProjectFileCacheProvider);
}
private static readonly Version ourVersion46 = new(4, 6);
private static (CSharpLanguageLevel? languageLevel, CSharpLanguageLevel? latestAvailableLanguageValue,CSharpLanguageLevel? previewAvailableLanguageValue)
DetermineCSharpLanguageLevelOldUnity(IProject project, UnityProjectFileCacheProvider unityProjectFileCacheProvider)
{
CSharpLanguageLevel? languageLevel = null;
if (!unityProjectFileCacheProvider.IsLangVersionExplicitlySpecified(project) || IsLangVersionDefault())
{
// Support for https://bitbucket.org/alexzzzz/unity-c-5.0-and-6.0-integration
// See also https://github.com/JetBrains/resharper-unity/issues/50#issuecomment-257611218
if (project.Location.CombineWithShortName("CSharp70Support").ExistsDirectory)
languageLevel = CSharpLanguageLevel.CSharp70;
else if (project.Location.CombineWithShortName("CSharp60Support").ExistsDirectory)
languageLevel = CSharpLanguageLevel.CSharp60;
else
languageLevel = IsTargetFrameworkAtLeast46()
? CSharpLanguageLevel.CSharp60
: CSharpLanguageLevel.CSharp40;
}
// https://forum.unity.com/threads/would-the-roslyn-compiler-compile-c-8-0-preview.598069/
if (project.Location.CombineWithShortName("CSharp80Support").ExistsDirectory)
{
if (IsLangVersionLatest()) languageLevel ??= CSharpLanguageLevel.CSharp80;
}
bool IsLangVersionDefault()
{
// Older VSTU sets LangVersion to "default", which means a higher version than the expected C# 4 or 6
foreach (var configuration in project.ProjectProperties.ActiveConfigurations.Configurations)
{
if (configuration is ICSharpProjectConfiguration csharpConfiguration)
{
// CSharpLanguageVersion.LatestMajor is the enum value for "default"
// CSharpLanguageVersion.Latest is the enum value for "latest"
if (csharpConfiguration.LanguageVersion != CSharpLanguageVersion.LatestMajor)
return false;
}
}
return true;
}
bool IsLangVersionLatest()
{
foreach (var configuration in project.ProjectProperties.ActiveConfigurations.Configurations)
{
if (configuration is ICSharpProjectConfiguration csharpConfiguration)
{
if (csharpConfiguration.LanguageVersion == CSharpLanguageVersion.Latest)
return true;
}
}
return false;
}
bool IsTargetFrameworkAtLeast46() => project.GetCurrentTargetFrameworkId().Version >= ourVersion46;
return (languageLevel, languageLevel, languageLevel);
}
}
}