Post on 31-Jul-2015
Compiler Platformコード解析と C# の未来
C# とともに祝 15 周年岩永信之
++C++; // 未確認飛行 C
今日話すこと• 2015 世代で C# チームが提供するもの• C# 6.0• .NET Compiler Platform
• それがもたらす影響• 独自のアナイライザー作成• Code-Aware Library
• 課題• C# 7.0 以降にどうつながるか
C# 6.0.NET Compiler Platform.NET チーム、 C# チームが提供する“現状”
C# 6.0
• 自動プロパティの改善• expression-bodied 関数メンバー• 文字列補間• nameof 演算子• null 条件演算子• using static• 例外フィルター• catch/finally 句内での await 等々
正直なところ華はない• 機能追加よりも優先すべき
ことがあった• 限られた時間の中で
“ 2015” に間に合うもの
C# 6.0
• 自動プロパティの改善• expression-bodied 関数メンバー• 文字列補間• nameof 演算子• null 条件演算子• using static• 例外フィルター• catch/finally 句内での await 等々
正直なところ華はない• 機能追加よりも優先すべき
ことがあった• 限られた時間の中で
“ 2015” に間に合うもの
C# 6.0 の話は今日はほぼしません既存資料※をご覧ください
※ http://ufcpp.net/study/csharp/ap_ver6.htmlhttp://www.slideshare.net/ufcpp/csharp6
IDE 前提の機能 .NET Compiler Platform
IDE 前提の機能• nameof 演算子• 識別子名を文字列として取得できる機能
• 識別子でないものは nameof の中に書けない• 修正漏れとかをコンパイル エラーにできる
• IDE の中で真価を発揮• 修正漏れがあればリアルタイムに気付ける• リファクタリングの対象になる
• 識別子のリネームに追従する
nameof(x) == "x" コンパイルするだけならほぼただの文字列リテラル
Demo
https://github.com/ufcpp/UfcppSample/tree/master/Demo/2015/CompilerPlatform/NameofDemo
プログラミング言語の IDE 連携• C# = IDE の恩恵を強く受けれる言語• ( 今までも ) リアルタイム解析
常にバックグラウンドで解析走ってる• 「ビルド」操作した時のコンパイル時間短い• コンパイル時よりももっと早い段階で、常にどこに問題があるかわかる
• ( 今、さらに )nameof 演算子でこの傾向が強まる"x" : 文字列の中の意味をコンパイラーは知らない
実行してみて初めて問題がわかるnameof(x) : () の中の意味をコンパイラーが知ってる
リアルタイム解析の対象
.NET Compiler Platform
• C# コンパイラーを 1 から作り直した• コードネーム“ Roslyn” 、製品名“ .NET Compiler Platform”
• 単なるコンパイラーではなく、プラットフォーム化旧
source executable
新
source executable
ブラックボックス出力しか取れない
ホワイトボックス化中間データを取れるように
• 抽象構文木の取得・書き換え• 拡張性の提供
誰でも IDE と連携できる
プラットフォーム化が最優先• C# 6.0( 機能追加 ) < 作り直し ( プラットフォーム化 )• 作り直しで手いっぱいで大きいことまでできない• ( 作り直して保守が楽になったからこそ細かいことができる )
• C# 6.0• 悪く言えば、おまけ、華がない• 良く言えば、そんな大変な中よく新機能を 2015 に間に合わせた
実はおまけ
Compiler Platform誰でもコンパイラーの中身に触れられる誰でも IDE 連携できるまず、「作る側」の話
コード解析 (Visual Studio 標準 )
コンパイルはできるんだけど、人的ミスっぽいものを警告
アイテム テンプレートには多めに出しておいて、最後に整理して消す
人それぞれで流儀が違うけど、プロジェクト内ではそろえたい
コード解析 (Visual Studio 標準 )
緑の下線 ( 警告 ):可能な限り直すべきもの
半透明 (情報 ):問題はないけども、直しようがあるもの
using System
using System
コード解析 (Visual Studio 標準 )
• クイック アクション
直しようがあるものには直し方も提示、自動修正
電球マークが目印 )
一斉修正ドキュメント内全部プロジェクト内全部ソリューション内全部
(
デモ
プラットフォーム化• こういうコード解析を誰でも作れるように• 作れるもの :• アナライザー (analyzer)
• シンボル追加、メソッド追加、コンパイル時などのタイミングで• コードを解析して、エラー / 警告 /情報 を出す
• コード修正 (code fix)• 特定の エラー / 警告 /情報 に反応して• 修正方法を提示して• その場限り、ファイル内全部、プロジェクト内全部、ソリューション内全部
などをまとめて修正
コード解析の自作に必要なもの (1)
• Visual Studio 2015 SDK• 「 Visual Studio SDK 」とかで検索、ダウンロード
https://www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs.aspx
コード解析の自作に必要なもの (2)
• .NET Compiler Platform SDK Template• 「拡張機能と更新プログラム」で「 .NET Compiler 」とかで検索
コード解析の自作• テンプレート → Extensibility → Analyzer with Code Fix
デモ
コード解析本体
テスト
Visual Studio 拡張
(NuGet パッケージが作られる )
(F5 実行で Visual Studio をデバッグ起動 )
テンプレ通りの状態でアナライザーとコード修正が 1 つずつ
配布• VSIX (Visual Studio Extension※)形式• Visual Studio にインストール
• 全プロジェクトから使う• Visual Studio Galleryで検索・ダウンロード可能†
• NuGet形式• プロジェクト単位で参照
• 使う・使わないをプロジェクトごとに切り替え• ライブラリと同梱で配布可能
• NuGet Galleryで検索・ダウンロード可能†
※ VSI (Visual Studio Installer) の流れを組んでるから VSIX と言うみたいファイル構造的には必要なファイルを ZIP で固めただけ
† もちろん、 VSIX/NuGet パッケージをファイル配布しても OK
配布 : Visual Studio GalleryWebサイト Visual Studio上から検索
配布 : NuGet GalleryWebサイト Visual Studio上から検索
拡張である意味• Visual Studio/C# コンパイラー標準でない意味
• 特定分野の限定機能も提供できる⇔ 汎用• 実装方法をカスタマイズできる ⇔ 汎用性と利便性は両立しにくい• 時代遅れになったら辞めればいい ⇔ 足すより減らす方が難しい• 経験則的な機能※も提供できる ⇔ 確実な機能しか提供できない• 嫌なら使わなければいい ⇔ 嫌でも使わされる• ライブラリ固有事情を汲める ⇔ ライブラリのことは知らない
コンパイラーは…拡張なら…
※ 端的にいうと誤判定・判定漏れもあり得る
拡張である意味• Visual Studio/C# コンパイラー標準でない意味
• 特定分野の限定機能も提供できる⇔ 汎用• 実装方法をカスタマイズできる ⇔ 汎用性と利便性は両立しにくい• 時代遅れになったら辞めればいい ⇔ 足すより減らす方が難しい• 経験則的な機能※も提供できる ⇔ 確実な機能しか提供できない• 嫌なら使わなければいい ⇔ 嫌でも使わされる• ライブラリ固有事情を汲める ⇔ ライブラリのことは知らない
コンパイラーは…拡張なら…
※ 端的にいうと誤判定・判定漏れもあり得る
Code-Aware ライブラリCode-Aware:( ライブラリ利用側の ) 「コードまで理解する」「コードを意識した」
• ライブラリ固有の事情にそったアナライザーを• ライブラリ本体と同梱して配布
プラットフォーム化の可能性「作る側」になる人は多くない一般開発者への恩恵は何か「使う側」の話
作る人 <<[壁 ]<< 使う人• 「誰でも作れる」≠「誰もが作る」• 実際とのところ、作る人は少数
• だいたい、作るの結構大変• 一度作ったらしばらく手を入れずに使い続けれる
• 誰かが作って、みんなが使う• プラットフォームには上モノが乗って初めて価値が出る
• 「 PC はアプリがなければただの箱」と同じ理屈作りやすくなる → 作られる → 使う人が便利に
ここまで揃って初めて「目玉機能」になる
いくつか紹介• 「ただの箱」でないところを紹介• 現時点での話• まだまだスタート地点に立ったばかり• CTP までは変更が多くて追いにくかったし、 RC が出てまだ 1 ・ 2ヶ月• たまに、 Gallery を検索してみるといいと思う
• おおまかに分類• コード解析系• Code-Aware ライブラリ系• メタプログラミング系
いくつか紹介
What 何をしてくれるものか
WhyどうしてC#コンパイラー/VSの標準機能にならないのか
どうしてライブラリ+アナライザーなのか
Owner 拡張ツールの作者( 中心人物、会社、チーム )
コード解析系• 単純に静的コード解析+リファクタリング
C# Essentials
WhatC# 5.0を6.0化するのをガイドしてくれる
主に ?. と =>
Why C# 5.0→6.0 の移行期にしか要らない古い形式のままで使いたい人もいる
OwnerDustin Campbell
(C#/VBチームのプログラム マネジャー)
https://github.com/DustinCampbell/CSharpEssentials
C# Essentials
WhatC# 5.0を6.0化するのをガイドしてくれる
主に ?. と =>
Why C# 5.0→6.0 の移行期にしか要らない古い形式のままで使いたい人もいる
OwnerDustin Campbell
(C#/VBチームのプログラム マネジャー)
https://github.com/DustinCampbell/CSharpEssentials
StyleCop Analyzer
WhatStyleCopが提供していたコード解析機能をVS拡張に
Why 趣味を選ぶ ( 人によって流儀が違う )指示が細かすぎる
OwnerSam Harwell
Coverity (静的解析ツール ベンダー)社員、MS MVP
https://github.com/DotNetAnalyzers/StyleCopAnalyzers
NRefactory 6
WhatNRefactory (SharpDevelopのコード解析)がRoslyn実装に
(MonoDevelopのコード解析もNRefactory)
Why SharpDevelop チームが主導VS標準よりも細かい指示多め
Owner SharpDevelopチーム
https://visualstudiogallery.msdn.microsoft.com/68c1575b-e0bf-420d-a94b-1b0f4bcdcbcc
Code Cracker
Whatコミュニティ ベースでいくつかの便利機能を実装
var強制、Regexの静的解析など
Why コミュニティを中心に開発趣味を選ぶ
Owner数名のMS MVP
https://github.com/code-cracker/code-cracker
Code-Aware ライブラリ系• ライブラリ固有事情を汲み取り• そのライブラリ以外にとっては役に立たないコード解析
• 現状、あんまりいいのが見つからなかったので自作のを紹介
( デモ用 ) FluentArithmetic
Whatデモ用に、最低限の機能に絞ったCode-Awareライブラリ
1.Add(2).Mul(3)みたいな書き方で整数四則演算
Why .Dive(0) を認めないとか 1.Add(2) を 3 に修正したりとか
このライブラリ以外でまったく役に立たない
Owner 自作
https://github.com/ufcpp/UfcppSample/tree/master/Chapters/DevEnv/CodeAwareLibrarySample
( デモ用 ) FluentArithmetic
Whatデモ用に、最低限の機能に絞ったCode-Awareライブラリ
1.Add(2).Mul(3)みたいな書き方で整数四則演算
Why .Dive(0) を認めないとか 1.Add(2) を 3 に修正したりとか
このライブラリ以外でまったく役に立たない
Owner 自作
https://github.com/ufcpp/UfcppSample/tree/master/Chapters/DevEnv/CodeAwareLibrarySample
0割りエラー
リテラル同士の演算を短縮
LazyMixin
What構造体を他の型に埋め込んで使う
has-aな実装で、is-a的な体験を提供
Why ライブラリだけで縛れない規約が多すぎるダメな書き方をエラーに、推奨の書き方をコード生成
Owner 自作
https://github.com/ufcpp/LazyMixinもっと汎用的な仕組みに書き換え中 : https://github.com/ufcpp/MixinGenerator
LazyMixin
What構造体を他の型に埋め込んで使う
has-aな実装で、is-a的な体験を提供
Why ライブラリだけで縛れない規約が多すぎるダメな書き方をエラーに、推奨の書き方をコード生成
Owner 自作
https://github.com/ufcpp/LazyMixinもっと汎用的な仕組みに書き換え中 : https://github.com/ufcpp/MixinGenerator
readonly がついていると意図しない動作になるのでエラーにする
推奨の使い方をコード生成
他にこんな使い方できそう• JSON ライブラリで、文字列リテラル中の JSON 解析を同梱• LINQ Provider に「使える式は何」警告機能を付ける。
メタプログラミング系• コード修正というより、コード生成• アナライザー実装にすることで• コード生成元も C# ⇔ T4 テンプレート : 元がキモイ
• コード生成結果が見える ⇔ PostSharp など : 生成結果が見えない
• 現状、あんまりいいのが見つからなかったので自作等を紹介
わかりやすさ大事。特に、継続的に保守する場合
見えないとデバッグが大変。何が原因か追えない
NotifyPropertyChangedGenerator
WhatINotifyPropertyChanged実装をコード生成
Why 特定用途すぎるし、実装方法にバリエーションがある
手書きがむちゃくちゃ大変
Owner neuecc (MS MVP)
https://github.com/neuecc/NotifyPropertyChangedGenerator
NotifyPropertyChangedGenerator
WhatINotifyPropertyChanged実装をコード生成
Why 特定用途すぎるし、実装方法にバリエーションがある
手書きがむちゃくちゃ大変
Owner neuecc (MS MVP)
https://github.com/neuecc/NotifyPropertyChangedGenerator
RecordConstructorGenerator
Whatimmutableな型のコンストラクターを書くのだるいので
コード生成するようにした
Why C# ができた当初 (15年前 ) には考慮に欠けてた
C# 7.0で状況改善しそうだけども、それまでのつなぎに
Owner 自作
https://github.com/ufcpp/RecordConstructorGenerator
RecordConstructorGenerator
Whatimmutableな型のコンストラクターを書くのだるいので
コード生成するようにした
Why C# ができた当初 (15年前 ) には考慮に欠けてた
C# 7.0で状況改善しそうだけども、それまでのつなぎに
Owner 自作
https://github.com/ufcpp/RecordConstructorGenerator
将来の話まだまだ始まったばかりで課題だらけこれから充実させていってほしいものもあるC# 7.0
IDE 連携 ≠ Visual Studio 連携• Q. で、 Visual Studio 以外で使えるの?• A. Roslynオープンソース化の意味がここで強く効いてくる• 現状、 Visual Studio のみだけど• Xamarin Studio : 対応中みたい• OmniSharp : Roslyn化フォークがある、作業中
• ATOM, Emacs, Sublime Text, Vim, ...
• Visual Studio Code : ATOM 実装 ( コード解析は OmniSharp任せ )
難易度 : 「可能にはなった」程度• コンパイラーの中身に触れるようになっただけ
var props = propertyNames.Select(p => new Property(p)).ToArray();
var docComment = GenerateDocComment(props.Select(p => p.Name));var parameterList = ParameterList().AddParameters(props.Select(p => p.ToParameter()).ToArray());var body = Block().AddStatements(props.Select(p => p.ToAssignment()).ToArray());
return ConstructorDeclaration(typeName) .WithModifiers(SyntaxTokenList.Create(PublicToken)) .WithParameterList(parameterList) .WithLeadingTrivia(docComment) .WithBody(body) .WithAdditionalAnnotations(Formatter.Annotation);
•読めた代物じゃない•書くのも大変
/// <summary>Record Constructor</summary>/// <param name="name"><see cref="Name"/></param>/// <param name="x"><see cref="X"/></param>/// <param name="y"><see cref="Y"/></param>public Point(string name = default(string), int x = default(int), int y = default(int)){ Name = name; X = x; Y = y;}
解析用の新構文が欲しい•型のパターン マッチング構文が欲しい• C# 7.0 で入りそう (確度高め )
• ぶっちゃけ、“内需”だと思う
var id = statement.Left as IdentifierNameSyntax;if (id == null) continue;
if (id.Identifier.Text == p.Name) return;
特定の型の場合だけ処理アナライザーを書いているとこういうコードだらけに
if (statement.Left is IdentifierNameSyntax id && id.Identifier.Text == p.Name) return;
C# 7.0 提案パターン マッチis 演算子の拡張
構文欲しい 修正用 式ツリー•組み換え可能な、 Roslyn版式ツリーが求められる• 要望としては出ているものの、構文の具体案なし
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(Name.Upper), IdentifierName(Name.Lower))); 「 X = x 」みたいなもの
を作るだけでこの大変さ
`${Name.Upper} = ${Name.Lower};`こういう類のメタプログラミング構文が欲しい ( この構文は適当 )
参照がめんどくさい•普通のライブラリの参照方法
• アナライザーの参照
ファイルを参照
※
※ NuGet参照するには、 NuGet の startup スクリプトに参照設定を書かなきゃ行けない( アナライザー作成テンプレートには最初からそういう設定スクリプトが書かれてる )
ファイルを参照
メタプログラミングはまだ妥協的• メタプログラミングでは• 生成元・生成結果両方見えてほしい• ただ、元と結果は明確に分離したい
public class Sample1{ public string Name { get; set; } public int X { get; set; } public int Y { get; set; }}
public class Sample1 : INotifyPropertyChanged{ public int X { get { return x; } set { SetProperty(ref x, value, xPropertyChangedEventArgs); } } public int Y { get { return y; } set { SetProperty(ref y, value, yPropertyChangedEventArgs); } }
#region NotifyPropertyChangedGenerator
public event PropertyChangedEventHandler PropertyChanged;
private int x; private static readonly PropertyChangedEventArgs xPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(X)); private int y; private static readonly PropertyChangedEventArgs yPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(Y));
元情報
生成結果• デバッグのためだけに見たい• コードを書く上ではノイズ
コンパイラーの負の遺産• Visual Studio/C# コンパイラー標準でない意味 (再掲、抜粋 )
• 時代遅れになったら辞めればいい ⇔ 足すより減らす方が難しい• 経験則的な機能※も提供できる ⇔ 確実な機能しか提供できない
• C# 7.0 提案• null非許容参照型• メソッド コントラクト
コンパイラーは…拡張なら…
この辺りの制限がきつくて足せない機能がある
• 互換性を崩さないのが無理• 100%確実な判定が無理
null非許容参照型• 今の C# に欠けているもの
値型 参照型
許容 T? T
非許容 Tこれがない
static void F(string s){ if (s == null) throw new ArgumentNullException(nameof(s));}
現状の書き方メソッドのシグネチャだけ見てnull許容かどうか判定できない
煩雑
null非許容参照型• 今の C# に欠けているもの
値型 参照型
許容 T? T?
非許容 T T!
null非許容参照型
static void F(string! s){}
C# 7.0 提案の書き方 • 誰が見ても null非許容• null チェックはコンパイラー生
成
null非許容参照型と互換性問題• F(string s) を F(string! s) に変えると、利用側を壊す• 今まで null チェックをサボっていた人がいたら• 例外 catch で済ませている人がいたら
• 標準ライブラリにちゃんと ! が付いてないと利便性半減互換性と利便性にトレードオフ
null非許容参照型と確実性•一時的に null になっていないといけない場面がある• 配列とか
• 特に、コレクションの実装とかで• List<T>• HashSet<T>
• この性質と、マルチスレッド動作が合わさると判定不能
var array = new string[N];for (var i = 0; i < N; i++){ array[i] = "";}
この間は絶対に null
最初に大きめの配列をとっておいて、そのうち Nマスだけ使う
null非許容参照型のアナライザー実装• アナライザーでなら実装簡単• 互換性が必要な場面では使わなければいい• 誤判定のリスク < ないことによる不便
•事実、実装がある• 今でも、 ReSharper とかの静的解析ツールはやってる• C# チームも、一度アナライザーで実装してみてる
• 実装 : https://github.com/mattwar/nullaby• その報告 : https://github.com/dotnet/roslyn/issues/2119
• もしかしたら、 C# 7.0 はこのまま、一部アナライザー実装になるかも
言語機能のアナライザー実装の課題•一部分だけアナライザー?• string! ← こういう書き方を解釈するのはコンパイラー機能• string! の非 null を解析するのはアナライザー
• 機能が on/off できるコンパイラー機能?• 同じバージョンの C# を使っているはずでも、コンパイルできる環境
とできない環境ができる
挙動的にはかなりキモい
まとめ
まとめ• C# 6.0 、 Compiler Platform• IDE との連携性が実は主役、 C# 6.0 はちょい役
• Platform 化の恩恵• 皆が作れる• Code-Aware である
• ライブラリ固有の事情をくめる• 経験則
• 特定文脈によった解析とか、誤判定 (判定漏れ ) が許容されうる
• C#公式機能ですらアナライザーベースになるかも• null非許容参照型 ( あとたぶん、メソッド コントラクトも )